class ScanWindow(ScanWindowBase): def __init__(self, operator, scan_name='scan', parent=None): self.logger = logging.getLogger(__name__) super().__init__(parent) self.setWindowTitle('Analog Discovery 2') self.operator = operator self.scan_name = scan_name # # For loading a .ui file (created with QtDesigner): # p = os.path.dirname(__file__) # uic.loadUi(os.path.join(p, 'design/UI/main_window.ui'), self) self.set_UI() # create thread and timer objects for scan self.scan_timer = QTimer(timeout=self.update_scan) self.scan_thread = WorkThread(self.operator.do_scan) def set_UI(self): """ Code-based generation of the user-interface based on PyQT """ self.setWindowTitle('Digilent AD2 Scan example') # display statusbar self.statusBar() ### The menu bar: mod_config_action = QAction("Con&fig", self, triggered=self.mod_scan_config, shortcut="Ctrl+Shift+C", statusTip='Modify the scan config') quit_action = QAction("&Close", self, triggered=self.close, shortcut="Ctrl+W", statusTip='Close the scan window') mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('&File') fileMenu.addAction(mod_config_action) fileMenu.addAction(quit_action) ### General layout central_widget = QWidget() central_layout = QHBoxLayout(central_widget) # Layout for left hand controls control_layout = QVBoxLayout() ### Scan box self.box_scan = QGroupBox('Scan') layout_scan = QVBoxLayout() self.box_scan.setLayout(layout_scan) control_layout.addWidget(self.box_scan) layout_scan_form = QFormLayout() layout_scan.addLayout(layout_scan_form) layout_scan_buttons = QHBoxLayout() layout_scan.addLayout(layout_scan_buttons) self.scan_start_spinbox = QDoubleSpinBox( suffix='V', minimum=-100, singleStep=0.001, valueChanged=self.scan_start_value) # self.scan_start_spinbox.valueChanged.connect(self.scan_start_value) self.scan_stop_spinbox = QDoubleSpinBox( suffix='V', minimum=-100, singleStep=0.001, valueChanged=self.scan_stop_value) self.scan_step_spinbox = QDoubleSpinBox( suffix='V', minimum=-100, singleStep=0.001, valueChanged=self.scan_step_value) self.scan_start_label = QLabel('start') self.scan_stop_label = QLabel('stop') self.scan_step_label = QLabel('step') layout_scan_form.addRow(self.scan_start_label, self.scan_start_spinbox) layout_scan_form.addRow(self.scan_stop_label, self.scan_stop_spinbox) layout_scan_form.addRow(self.scan_step_label, self.scan_step_spinbox) self.start_button = QPushButton('Start', clicked=self.start_scan) self.pause_button = QPushButton('Pause', clicked=self.pause_scan) self.stop_button = QPushButton('Stop', clicked=self.stop_scan) self.kill_button = QPushButton('Kill', clicked=self.kill_scan) # Haven't decided what names are best. Suggestions: # start, pause, interrupt, stop, abort, quit, kill layout_scan_buttons.addWidget(self.start_button) layout_scan_buttons.addWidget(self.pause_button) layout_scan_buttons.addWidget(self.stop_button) layout_scan_buttons.addWidget(self.kill_button) self.saver = SaverWidget(self.operator.save_scan) layout_scan.addWidget(self.saver) ### Graphs: self.graph_win = pg.GraphicsWindow() self.graph_win.resize(1000, 600) self.plot1 = self.graph_win.addPlot() self.curve1 = self.plot1.plot(pen='y') # Add an empty widget at the bottom of the control layout to make layout nicer dummy = QWidget() dummy.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) control_layout.addWidget(dummy) # Add control layout and graph window to central layout and apply central layout to window central_layout.addLayout(control_layout) central_layout.addWidget(self.graph_win) self.setCentralWidget(central_widget) self.apply_properties() self.reset_fields() def mod_scan_config(self): """ Open the Modify Config window for the scan properties """ conf_win = ModifyConfig(self.operator.properties[self.scan_name], apply_callback=self.apply_properties, parent=self) conf_win.show() def apply_properties(self): """ Apply properties dictionary to gui elements. """ self.logger.debug('Applying config properties to gui elements') self.operator._set_scan_start(self.operator.properties[ self.scan_name]['start']) # this optional line checks validity self.scan_start_spinbox.setValue( self.operator.properties[self.scan_name]['start']) self.operator._set_scan_stop(self.operator.properties[ self.scan_name]['stop']) # this optional line checks validity self.scan_stop_spinbox.setValue( self.operator.properties[self.scan_name]['stop']) self.operator._set_scan_step(self.operator.properties[ self.scan_name]['step']) # this optional line checks validity self.scan_step_spinbox.setValue( self.operator.properties[self.scan_name]['step']) if 'title' in self.operator.properties[self.scan_name]: self.box_scan.setTitle( self.operator.properties[self.scan_name]['title']) self.plot1.setTitle( self.operator.properties[self.scan_name]['title']) self.plot1.setLabel( 'bottom', self.operator.properties[self.scan_name]['x_label'], units=self.operator.properties[self.scan_name]['x_units']) self.plot1.setLabel( 'left', self.operator.properties[self.scan_name]['y_label'], units=self.operator.properties[self.scan_name]['y_units']) self.plot1.setXRange(self.operator.properties[self.scan_name]['start'], self.operator.properties[self.scan_name]['stop']) if 'filename' in self.operator.properties[self.scan_name]: self.saver.filename.setText( self.operator.properties[self.scan_name]['filename']) def scan_start_value(self): """ Called when Scan Start spinbox is modified. Updates the parameter using a method of operator (which checks validity and also fixes the sign of step) and forces the (corrected) parameter in the gui elements """ self.operator._set_scan_start(self.scan_start_spinbox.value()) self.scan_start_spinbox.setValue( self.operator.properties[self.scan_name]['start']) self.scan_step_spinbox.setValue( self.operator.properties[self.scan_name]['step']) set_spinbox_stepsize(self.scan_start_spinbox) self.plot1.setXRange(self.operator.properties[self.scan_name]['start'], self.operator.properties[self.scan_name]['stop']) def scan_stop_value(self): """ Called when Scan Stop spinbox is modified. Updates the parameter using a method of operator (which checks validity and also fixes the sign of step) and forces the (corrected) parameter in the gui elements """ self.operator._set_scan_stop(self.scan_stop_spinbox.value()) self.scan_stop_spinbox.setValue( self.operator.properties[self.scan_name]['stop']) self.scan_step_spinbox.setValue( self.operator.properties[self.scan_name]['step']) set_spinbox_stepsize(self.scan_stop_spinbox) self.plot1.setXRange(self.operator.properties[self.scan_name]['start'], self.operator.properties[self.scan_name]['stop']) def scan_step_value(self): """ Called when Scan Step spinbox is modified. Updates the parameter using a method of operator (which checks validity) and forces the (corrected) parameter in the gui element """ self.operator._set_scan_step(self.scan_step_spinbox.value()) self.scan_step_spinbox.setValue( self.operator.properties[self.scan_name]['step']) set_spinbox_stepsize(self.scan_step_spinbox) def reset_fields(self): """ Resets gui elements after a scan is finished, stopped or terminated. """ self.start_button.setEnabled(True) self.pause_button.setText('Pause') self.pause_button.setEnabled(False) self.stop_button.setEnabled(False) self.scan_start_spinbox.setEnabled(True) self.scan_stop_spinbox.setEnabled(True) self.scan_step_spinbox.setEnabled(True) # Reset all flow control flags self.operator._busy = False self.operator._pause = False self.operator._stop = False def start_scan(self): """ Called when start button is pressed. Starts the monitor (thread and timer) and disables some gui elements """ if self.operator._busy: self.logger.debug("Operator is busy") return else: self.logger.debug('Starting scan') self.start_button.setEnabled(False) self.pause_button.setEnabled(True) self.stop_button.setEnabled(True) # self.operator._stop = False # enable operator monitor loop to run self.scan_thread.start() # start the operator monitor self.scan_timer.start(self.operator.properties[ self.scan_name]['gui_refresh_time']) # start the update timer self.scan_start_spinbox.setEnabled(False) self.scan_stop_spinbox.setEnabled(False) self.scan_step_spinbox.setEnabled(False) def pause_scan(self): """ Called when pause button is clicked. Signals the operator scan to pause. Updates buttons accordingly """ if not self.operator._pause: self.operator._pause = True self.pause_button.setText('Continue') else: self.operator._pause = False self.pause_button.setText('Pause') def stop_scan(self): """ Stop all loop threads: - flags the operator to stop - uses the Workthread stop method to wait a bit for the operator to finish, or terminate thread if timeout occurs """ self.logger.debug('Stopping operator') self.stop_button.setEnabled(False) self.operator._stop = True if self.scan_thread.isRunning(): self.scan_thread.stop( self.operator.properties[self.scan_name]['stop_timeout']) self.operator._busy = False # Reset in case the monitor was not stopped gracefully, but forcefully stopped self.reset_fields() def kill_scan(self): """ Forcefully terminates the scan thread """ self.logger.debug('Killing operator threads') self.operator._stop = True self.scan_thread.terminate() self.reset_fields() def update_scan(self): """ Checks if new data is available and updates the graph. Checks if thread is still running and if not: stops timer and reset gui elements (called by timer) """ if self.operator._new_scan_data: self.operator._new_scan_data = False self.curve1.setData(self.operator.scan_voltages, self.operator.measured_voltages) if self.scan_thread.isFinished(): self.logger.debug('Scan thread is finished') self.scan_timer.stop() self.reset_fields() def closeEvent(self, event): """ Gets called when the window is closed. Could be used to do some cleanup before closing. """ # # Use this bit to display an "Are you sure"-dialogbox # quit_msg = "Are you sure you want to exit labphew monitor?" # reply = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No) # if reply == QMessageBox.No: # event.ignore() # return self.stop_scan() # stop scan self.scan_timer.stop() # stop scan timer, just to be sure event.accept()
class ScanWindow(ScanWindowBase): def __init__(self, operator, parent=None): self.logger = logging.getLogger(__name__) super().__init__(parent) self.setWindowTitle('Blink Scan') self.operator = operator # # For loading a .ui file (created with QtDesigner): # p = os.path.dirname(__file__) # uic.loadUi(os.path.join(p, 'design/UI/main_window.ui'), self) self.set_UI() # create thread and timer objects for scan self.scan_timer = QTimer(timeout=self.update_scan) self.scan_thread = WorkThread(self.operator.do_scan) def set_UI(self): """ Code-based generation of the user-interface based on PyQT """ self.setWindowTitle('Blink Scan example') # display statusbar self.statusBar() ### The menu bar: mod_config_action = QAction("Con&fig", self, triggered=self.mod_scan_config, shortcut="Ctrl+Shift+C", statusTip='Modify the scan config') save_action = QAction("&Save", self, triggered=self.save, shortcut="Ctrl+S", statusTip='Save the scan data') quit_action = QAction("&Close", self, triggered=self.close, shortcut="Ctrl+W", statusTip='Close the scan window') self.start_action = QAction("&Start", self, triggered=self.start_scan, shortcut="F5", statusTip='Start the Scan') self.pause_action = QAction("&Pause", self, triggered=self.pause_scan, shortcut="Ctrl+P", statusTip='Pause the scan', enabled=False) self.stop_action = QAction("S&top", self, triggered=self.stop_scan, shortcut="Ctrl+A", statusTip='Stop the scan after current iteration', enabled=False) self.kill_action = QAction("&Kill", self, triggered=self.kill_scan, shortcut="Ctrl+K", statusTip='Immediately kill the scan') mainMenu = self.menuBar() fileMenu = mainMenu.addMenu('&File') fileMenu.addAction(mod_config_action) fileMenu.addAction(save_action) fileMenu.addAction(quit_action) runMenu = mainMenu.addMenu('&Run Scan') runMenu.addAction(self.start_action) runMenu.addAction(self.pause_action) runMenu.addAction(self.stop_action) runMenu.addAction(self.kill_action) self.graph_win = pg.GraphicsWindow() self.graph_win.resize(800, 500) self.plot1 = self.graph_win.addPlot() self.curve1 = self.plot1.plot(pen='w') self.plot1.setLabel('bottom', 'data points') self.plot1.setLabel('left', 'state') self.setCentralWidget(self.graph_win) def mod_scan_config(self): """ Open the Modify Config window for the scan properties """ conf_win = ModifyConfig(self.operator.properties['scan'], apply_callback=None, parent=self) conf_win.show() def save(self): print(self.operator.properties['scan']['filename']) try: fname = QFileDialog.getSaveFileName(self, 'Save data as', self.operator.properties['scan']['filename'], filter="netCDF4 (*.nc);;All Files (*.*)") except: fname = QFileDialog.getSaveFileName(self, 'Save data as', os.path.join(labphew.parent_path, 'data.nc'), filter="netCDF4 (*.nc);;All Files (*.*)") if fname[0]: self.operator.save_scan(fname[0]) def reset_fields(self): """ Resets gui elements after a scan is finished, stopped or terminated. """ self.start_action.setEnabled(True) self.pause_action.setEnabled(False) self.stop_action.setEnabled(False) self.pause_action.setText('Pause') self.operator._busy = False self.operator._pause = False self.operator._stop = False def start_scan(self): """ Called when start button is pressed. Starts the monitor (thread and timer) and disables some gui elements """ if self.operator._busy: self.logger.debug("Operator is busy") return else: self.logger.debug('Starting scan') self.start_action.setEnabled(False) self.pause_action.setEnabled(True) self.stop_action.setEnabled(True) # self.operator._stop = False # enable operator monitor loop to run self.scan_thread.start() # start the operator monitor # Start the update timer with time specified in config if available try: self.scan_timer.start(self.operator.properties['scan']['gui_refresh_time']) except: self.scan_timer.start(0.05) # otherwise use default value def pause_scan(self): """ Called when pause button is clicked. Signals the operator scan to pause. Updates buttons accordingly """ if not self.operator._pause: self.operator._pause = True self.pause_action.setText('&Continue') else: self.operator._pause = False self.pause_action.setText('Pause') def stop_scan(self): """ Stop all loop threads: - flags the operator to stop - uses the Workthread stop method to wait a bit for the operator to finish, or terminate thread if timeout occurs """ self.logger.debug('Stopping operator') # self.stop_button.setEnabled(False) self.operator._stop = True if self.scan_thread.isRunning(): self.scan_thread.stop() # Stop thread with default timeout before killing it self.operator._busy = False # Reset in case the monitor was not stopped gracefully, but forcefully stopped self.reset_fields() def kill_scan(self): """ Forcefully terminates the scan thread """ self.logger.debug('Killing operator threads') self.operator._stop = True self.scan_thread.terminate() self.reset_fields() def update_scan(self): """ Checks if new data is available and updates the graph. Checks if thread is still running and if not: stops timer and reset gui elements (called by timer) """ if self.operator._new_scan_data: self.operator._new_scan_data = False self.curve1.setData(self.operator.point_number, self.operator.measured_state) if self.scan_thread.isFinished(): self.logger.debug('Scan thread is finished') self.scan_timer.stop() self.reset_fields() def closeEvent(self, event): """ Gets called when the window is closed. Could be used to do some cleanup before closing. """ # # Use this bit to display an "Are you sure"-dialogbox # quit_msg = "Are you sure you want to exit labphew monitor?" # reply = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No) # if reply == QMessageBox.No: # event.ignore() # return self.stop_scan() # stop scan self.scan_timer.stop() # stop scan timer, just to be sure event.accept()