class LogScaleIntensity(ComposableItemImageView): def __init__(self, *args, **kwargs): # Composes a new type consisting of any ImageItem types in imageItem_bases with this classes's helper ImageItem # class (LogScaleImageItem) self.imageItem_bases += (LogScaleImageItem,) imageItem = type("DynamicImageItem", tuple(self.imageItem_bases), {})() if "imageItem" in kwargs: del kwargs["imageItem"] super(LogScaleIntensity, self).__init__(imageItem=imageItem, *args, **kwargs) self.logScale = True # Setup log scale button self.logIntensityButton = QPushButton("Log Intensity") sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.logIntensityButton.sizePolicy().hasHeightForWidth()) self.logIntensityButton.setSizePolicy(sizePolicy) self.logIntensityButton.setObjectName("logIntensity") self.ui.gridLayout.addWidget(self.logIntensityButton, 3, 2, 1, 1) self.logIntensityButton.setCheckable(True) self.setLogScale(True) self.logIntensityButton.clicked.connect(self._setLogScale) def _setLogScale(self, value): self.imageItem.logScale = value self.imageItem.qimage = None self.imageItem.update() self.getHistogramWidget().region.setBounds([0 if value else None, None]) def setLogScale(self, value): self._setLogScale(value) self.logIntensityButton.setChecked(value)
def setup_btn_grid(self, n_lights_1axis): """Set up grid of buttons.""" table = self.btn_grid_table if n_lights_1axis != table.rowCount(): table.clear() table.setSelectionMode(QAbstractItemView.NoSelection) table.setColumnCount(n_lights_1axis) table.setRowCount(n_lights_1axis) table.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed) table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed) table.horizontalHeader().setDefaultSectionSize(CELL_SIZE) table.verticalHeader().setDefaultSectionSize(CELL_SIZE) table.horizontalHeader().hide() table.verticalHeader().hide() for idx_row in range(n_lights_1axis): for idx_col in range(n_lights_1axis): btn = QPushButton(self) btn.setStyleSheet(BTN_STYLE) btn.setCheckable(True) btn.setChecked(True) btn.clicked.connect( self.clicked_btn_of_grid_factory(idx_row, idx_col)) table.setCellWidget(idx_row, idx_col, btn) self.show_solution()
class LogButtons(BetterLayout): """Button mixin that can toggle x/y log modes.""" def __init__(self, *args, **kwargs): super(LogButtons, self).__init__(*args, **kwargs) # Define single-source of text state self.X_ON_TEXT = "X Log Mode On" self.X_OFF_TEXT = "X Log Mode Off" self.Y_ON_TEXT = "Y Log Mode On" self.Y_OFF_TEXT = "Y Log Mode Off" # Create checkable buttons self.x_log_button = QPushButton(self.X_OFF_TEXT) self.x_log_button.setCheckable(True) self.x_log_button.toggled.connect(self.set_x_log_mode) self.y_log_button = QPushButton(self.Y_OFF_TEXT) self.y_log_button.setCheckable(True) self.y_log_button.toggled.connect(self.set_y_log_mode) # Update button check state when pyqtgraph log x checkbox is toggled by user self.getPlotItem().ctrl.logXCheck.toggled.connect( self._update_x_button) # Update button check state when pyqtgraph log y checkbox is toggled by user self.getPlotItem().ctrl.logYCheck.toggled.connect( self._update_y_button) # Create a layout to have these buttons side-by-side layout = QHBoxLayout() layout.addWidget(self.x_log_button) layout.addWidget(self.y_log_button) # TODO: BetterLayout # RightLayout BottomLayout # add_widget add_widget self.add_widget_to_bottom(layout) def _update_y_button(self, state: bool): self.y_log_button.setChecked(state) if state: self.y_log_button.setText(self.Y_ON_TEXT) else: self.y_log_button.setText(self.Y_OFF_TEXT) def _update_x_button(self, state: bool): self.x_log_button.setChecked(state) if state: self.x_log_button.setText(self.X_ON_TEXT) else: self.x_log_button.setText(self.X_OFF_TEXT) def set_x_log_mode(self, state: bool): self._update_x_button(state) # Grab existing x log state from pyqtgraph y_log_mode = self.getPlotItem().ctrl.logYCheck.isChecked() self.setLogMode(x=state, y=y_log_mode) def set_y_log_mode(self, state: bool): self._update_y_button(state) # Grab existing y log state from pyqtgraph x_log_mode = self.getPlotItem().ctrl.logXCheck.isChecked() self.setLogMode(x=x_log_mode, y=state)
def add_canvas_display_widgets(): for i in range(len(self.canvas_display_widgets)): # Add the view to the stacked widget self.stackedwidget.addWidget(self.canvas_display_widgets[i]) # Create a button, using the view's recommended display icon button = QPushButton(self) button.setCheckable(True) button.setIcon(self.canvas_display_widgets[i].icon) # Add the button to the logical button group self.buttongroup.addButton(button, i) # Add the button to the visual layout section self.buttonpanel.addWidget(button)
class DataSelector(QWidget): """ Simple button to create and hold information about a selection area in a pyqtgraph.PlotItem object """ new_selection = Signal(float, float) abort_selection = Signal() def __init__(self, plotitem, *args, **kwargs): super().__init__(*args, **kwargs) self.plot = plotitem self.sele = None self.setLayout(QHBoxLayout()) # remove annoying padding self.layout().setContentsMargins(0, 0, 0, 0) self.toggle = QPushButton(self) self.toggle.setText('Select Area') self.toggle.setCheckable(True) self.toggle.setChecked(False) self.layout().addWidget(self.toggle) self.toggle.toggled.connect(self.toggle_selection) def toggle_selection(self, activated): if activated: self.toggle.setText('Stop Selecting') sele = pg.LinearRegionItem() sele.sigRegionChangeFinished.connect(self.on_selection_changed) self.sele = sele self.plot.addItem(sele) else: self.toggle.setText('Select Area') if self.sele is not None: self.sele.sigRegionChangeFinished.disconnect() self.plot.removeItem(self.sele) self.sele = None self.on_selection_changed(None) def on_selection_changed(self, region_changed): if region_changed is None: self.abort_selection.emit() else: left, right = region_changed.getRegion() self.new_selection.emit(left, right) def toggle_enabled(self, enabled): self.toggle.setVisible(enabled) if not enabled: self.toggle_selection(False)
def create_autofit_group(self): auto_button = QPushButton(get_icon("apply.png"), _("Run"), self) auto_button.clicked.connect(self.autofit) autoprm_button = QPushButton(get_icon("settings.png"), _("Settings"), self) autoprm_button.clicked.connect(self.edit_parameters) xrange_button = QPushButton(get_icon("xrange.png"), _("Bounds"), self) xrange_button.setCheckable(True) xrange_button.toggled.connect(self.toggle_xrange) auto_layout = QVBoxLayout() auto_layout.addWidget(auto_button) auto_layout.addWidget(autoprm_button) auto_layout.addWidget(xrange_button) self.button_list += [auto_button, autoprm_button, xrange_button] return create_groupbox(self, _("Automatic fit"), layout=auto_layout)
def __init__(self, layer: Image, parent=None) -> None: super().__init__(parent=parent) self.setLayout(QHBoxLayout()) self.layout().setSpacing(2) self.layout().setContentsMargins(0, 0, 0, 0) once_btn = QPushButton(trans._('once')) once_btn.setFocusPolicy(Qt.NoFocus) auto_btn = QPushButton(trans._('continuous')) auto_btn.setCheckable(True) auto_btn.setFocusPolicy(Qt.NoFocus) once_btn.clicked.connect(lambda: auto_btn.setChecked(False)) connect_no_arg(once_btn.clicked, layer, "reset_contrast_limits") connect_setattr(auto_btn.toggled, layer, "_keep_autoscale") connect_no_arg(auto_btn.clicked, layer, "reset_contrast_limits") self.layout().addWidget(once_btn) self.layout().addWidget(auto_btn)
def createTopRightGroupBox(self): self.topRightGroupBox = QGroupBox("Group 2") defaultPushButton = QPushButton("Default Push Button") defaultPushButton.setDefault(True) togglePushButton = QPushButton("Toggle Push Button") togglePushButton.setCheckable(True) togglePushButton.setChecked(True) flatPushButton = QPushButton("Flat Push Button") flatPushButton.setFlat(True) layout = QVBoxLayout() layout.addWidget(defaultPushButton) layout.addWidget(togglePushButton) layout.addWidget(flatPushButton) layout.addStretch(1) self.topRightGroupBox.setLayout(layout)
class ToggleSymbols(BetterLayout): """Simple mixin that adds a button to toggle 'o' symbols on plot data curves in the plot widget.""" def __init__(self, *args, **kwargs): super(ToggleSymbols, self).__init__(*args, **kwargs) self.toggle_symbols_button = QPushButton("Toggle Symbols") self.toggle_symbols_button.setCheckable(True) self.add_widget_to_right(self.toggle_symbols_button) self.toggle_symbols_button.toggled.connect(self._toggle_symbol) self._symbol_cache = [] def _toggle_symbol(self, checked: bool): for item in self.scene().items(): if isinstance(item, pg.PlotDataItem): if checked: item.setData(symbol=None) else: item.setData(symbol='o')
class DimSwitch(QWidget): dimChanged = Signal(bool) def __init__(self, parent=None): super().__init__(parent) layout = QHBoxLayout() layout.addStretch(1) self.button_2d = QPushButton("2D") self.button_2d.setCheckable(True) self.button_2d.setChecked(True) self.button_2d.clicked.connect(functools.partial(self.set_2D, True)) self.button_3d = QPushButton("3D") self.button_3d.setCheckable(True) self.button_3d.clicked.connect(functools.partial(self.set_2D, False)) layout.addWidget(self.button_2d) layout.addWidget(self.button_3d) layout.addStretch(1) self.setLayout(layout) def set_2D(self, bool2d): self.button_2d.setChecked(bool2d) self.button_3d.setChecked(not bool2d) self.dimChanged.emit(bool2d)
class FixableWidgetParameterItem(parameterTypes.WidgetParameterItem): def __init__(self, param, depth): super(FixableWidgetParameterItem, self).__init__(param, depth) if param.opts.get("fixable"): self.fixbutton = QPushButton() self.fixbutton.setFixedWidth(20) self.fixbutton.setFixedHeight(20) self.fixbutton.setCheckable(True) self.fixbutton.setChecked(param.opts["fixed"]) self.fixbutton.toggled.connect(param.sigFixToggled) self.fixbutton.toggled.connect( lambda fixed: param.setOpts(fixed=fixed)) # self.fixbutton.toggled.connect(lambda fixed: self.widgetValueChanged()) self.fixbutton.setIcon(QIcon(path("icons/anchor.png"))) self.layoutWidget.layout().addWidget(self.fixbutton) def optsChanged(self, param, opts): """Called when any options are changed that are not name, value, default, or limits""" # print "opts changed:", opts ParameterItem.optsChanged(self, param, opts) if "readonly" in opts: self.updateDefaultBtn() if isinstance(self.widget, (QCheckBox, ColorButton)): self.widget.setEnabled(not opts["readonly"]) ## If widget is a SpinBox, pass options straight through if isinstance(self.widget, SpinBox): if "units" in opts and "suffix" not in opts: opts["suffix"] = opts["units"] try: # patch passes silently for 'fixed' self.widget.setOpts(**opts) except TypeError: pass self.updateDisplayLabel()
class LogScaleIntensity(ImageView): def __init__(self, *args, **kwargs): if kwargs.get("imageItem") and not isinstance(kwargs.get("imageItem"), LogScaleImageItem): raise RuntimeError( "The imageItem set to a LogScaleIntensity ImageView must be a LogScaleImageItem." ) kwargs["imageItem"] = LogScaleImageItem() super(LogScaleIntensity, self).__init__(*args, **kwargs) self.logScale = True # Setup log scale button self.logIntensityButton = QPushButton("Log Intensity") sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.logIntensityButton.sizePolicy().hasHeightForWidth()) self.logIntensityButton.setSizePolicy(sizePolicy) self.logIntensityButton.setObjectName("logIntensity") self.ui.gridLayout.addWidget(self.logIntensityButton, 3, 2, 1, 1) self.logIntensityButton.setCheckable(True) self.setLogScale(True) self.logIntensityButton.clicked.connect(self._setLogScale) def _setLogScale(self, value): self.imageItem.logScale = value self.imageItem.qimage = None self.imageItem.update() self.getHistogramWidget().region.setBounds( [0 if value else None, None]) def setLogScale(self, value): self._setLogScale(value) self.logIntensityButton.setChecked(value)
class OffsetPlots(BetterLayout): """Create a visual offset in the plots""" # TODO: implement the offset code def __init__(self, *args, **kwargs): super(OffsetPlots, self).__init__(*args, **kwargs) self.offset_box = QDoubleSpinBox() self.offset_box.setMinimum(0.0) self.offset_box.setDecimals(1) self.offset_box.setSingleStep(0.1) self.offset_button = QPushButton("Enable Offset") self.offset_button.setCheckable(True) self.offset_button.toggled.connect(self._offset_toggled) layout = QHBoxLayout() layout.addWidget(self.offset_box) layout.addWidget(self.offset_button) self.add_widget_to_bottom(layout) def _offset_toggled(self, enabled): if enabled: self.offset_button.setText("Disable Offset") else: self.offset_button.setText("Enable Offset")
class RunDialog(QDialog): simulation_done = Signal(bool, str) def __init__(self, config_file, run_model, arguments, parent=None): QDialog.__init__(self, parent) self.setWindowFlags(Qt.Window) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.setModal(True) self.setWindowModality(Qt.WindowModal) self.setWindowTitle("Simulations - {}".format(config_file)) assert isinstance(run_model, BaseRunModel) self._run_model = run_model ert = None if isinstance(run_model, BaseRunModel): ert = run_model.ert() self._simulations_argments = arguments self.simulations_tracker = create_tracker( run_model, qtimer_cls=QTimer, event_handler=self._on_tracker_event, num_realizations=arguments["active_realizations"].count()) states = self.simulations_tracker.get_states() self.state_colors = {state.name: state.color for state in states} self.state_colors['Success'] = self.state_colors["Finished"] self.state_colors['Failure'] = self.state_colors["Failed"] self.total_progress = SimpleProgress() status_layout = QHBoxLayout() status_layout.addStretch() self.__status_label = QLabel() status_layout.addWidget(self.__status_label) status_layout.addStretch() status_widget_container = QWidget() status_widget_container.setLayout(status_layout) self.progress = Progress() self.progress.setIndeterminateColor(self.total_progress.color) for state in states: self.progress.addState(state.state, QColor(*state.color), 100.0 * state.count / state.total_count) legend_layout = QHBoxLayout() self.legends = {} for state in states: self.legends[state] = Legend("%s (%d/%d)", QColor(*state.color)) self.legends[state].updateLegend(state.name, 0, 0) legend_layout.addWidget(self.legends[state]) legend_widget_container = QWidget() legend_widget_container.setLayout(legend_layout) self.running_time = QLabel("") self.plot_tool = PlotTool(config_file) self.plot_tool.setParent(self) self.plot_button = QPushButton(self.plot_tool.getName()) self.plot_button.clicked.connect(self.plot_tool.trigger) self.plot_button.setEnabled(ert is not None) self.kill_button = QPushButton("Kill simulations") self.done_button = QPushButton("Done") self.done_button.setHidden(True) self.restart_button = QPushButton("Restart") self.restart_button.setHidden(True) self.show_details_button = QPushButton("Details") self.show_details_button.setCheckable(True) size = 20 spin_movie = resourceMovie("ide/loading.gif") spin_movie.setSpeed(60) spin_movie.setScaledSize(QSize(size, size)) spin_movie.start() self.processing_animation = QLabel() self.processing_animation.setMaximumSize(QSize(size, size)) self.processing_animation.setMinimumSize(QSize(size, size)) self.processing_animation.setMovie(spin_movie) button_layout = QHBoxLayout() button_layout.addWidget(self.processing_animation) button_layout.addWidget(self.running_time) button_layout.addStretch() button_layout.addWidget(self.show_details_button) button_layout.addWidget(self.plot_button) button_layout.addWidget(self.kill_button) button_layout.addWidget(self.done_button) button_layout.addWidget(self.restart_button) button_widget_container = QWidget() button_widget_container.setLayout(button_layout) self.detailed_progress = DetailedProgressWidget( self, self.state_colors) self.detailed_progress.setVisible(False) self.dummy_widget_container = QWidget( ) #Used to keep the other widgets from stretching layout = QVBoxLayout() layout.addWidget(self.total_progress) layout.addWidget(status_widget_container) layout.addWidget(self.progress) layout.addWidget(legend_widget_container) layout.addWidget(self.detailed_progress) layout.addWidget(self.dummy_widget_container) layout.addWidget(button_widget_container) layout.setStretch(0, 0) layout.setStretch(1, 0) layout.setStretch(2, 0) layout.setStretch(3, 0) layout.setStretch(4, 1) layout.setStretch(5, 1) layout.setStretch(6, 0) self.setLayout(layout) self.kill_button.clicked.connect(self.killJobs) self.done_button.clicked.connect(self.accept) self.restart_button.clicked.connect(self.restart_failed_realizations) self.show_details_button.clicked.connect(self.toggle_detailed_progress) self.simulation_done.connect(self._on_simulation_done) def reject(self): return def closeEvent(self, QCloseEvent): self.simulations_tracker.stop() if self._run_model.isFinished(): self.simulation_done.emit(self._run_model.hasRunFailed(), self._run_model.getFailMessage()) else: # Kill jobs if dialog is closed if self.killJobs() != QMessageBox.Yes: QCloseEvent.ignore() def startSimulation(self): self._run_model.reset() self.simulations_tracker.reset() def run(): self._run_model.startSimulations(self._simulations_argments) simulation_thread = Thread(name="ert_gui_simulation_thread") simulation_thread.setDaemon(True) simulation_thread.run = run simulation_thread.start() self.simulations_tracker.track() def killJobs(self): msg = "Are you sure you want to kill the currently running simulations?" if self._run_model.getQueueStatus().get( JobStatusType.JOB_QUEUE_UNKNOWN, 0) > 0: msg += "\n\nKilling a simulation with unknown status will not kill the realizations already submitted!" kill_job = QMessageBox.question(self, "Kill simulations?", msg, QMessageBox.Yes | QMessageBox.No) if kill_job == QMessageBox.Yes: if self._run_model.killAllSimulations(): self.reject() return kill_job @Slot(bool, str) def _on_simulation_done(self, failed, failed_msg): self.simulations_tracker.stop() self.processing_animation.hide() self.kill_button.setHidden(True) self.done_button.setHidden(False) self.restart_button.setVisible(self.has_failed_realizations()) self.restart_button.setEnabled(self._run_model.support_restart) if failed: QMessageBox.critical( self, "Simulations failed!", "The simulation failed with the following " + "error:\n\n{}".format(failed_msg)) @Slot(object) def _on_tracker_event(self, event): if isinstance(event, TickEvent): self.running_time.setText(format_running_time(event.runtime)) if isinstance(event, GeneralEvent): self.total_progress.setProgress(event.progress) self.progress.setIndeterminate(event.indeterminate) if event.indeterminate: for state in event.sim_states: self.legends[state].updateLegend(state.name, 0, 0) else: for state in event.sim_states: self.progress.updateState( state.state, 100.0 * state.count / state.total_count) self.legends[state].updateLegend(state.name, state.count, state.total_count) if isinstance(event, DetailedEvent): if not self.progress.get_indeterminate(): self.detailed_progress.set_progress(event.details, event.iteration) if isinstance(event, EndEvent): self.simulation_done.emit(event.failed, event.failed_msg) def has_failed_realizations(self): completed = self._run_model.completed_realizations_mask initial = self._run_model.initial_realizations_mask for (index, successful) in enumerate(completed): if initial[index] and not successful: return True return False def count_successful_realizations(self): """ Counts the realizations completed in the prevoius ensemble run :return: """ completed = self._run_model.completed_realizations_mask return completed.count(True) def create_mask_from_failed_realizations(self): """ Creates a BoolVector mask representing the failed realizations :return: Type BoolVector """ completed = self._run_model.completed_realizations_mask initial = self._run_model.initial_realizations_mask inverted_mask = BoolVector(default_value=False) for (index, successful) in enumerate(completed): inverted_mask[index] = initial[index] and not successful return inverted_mask def restart_failed_realizations(self): msg = QMessageBox(self) msg.setIcon(QMessageBox.Information) msg.setText( "Note that workflows will only be executed on the restarted realizations and that this might have unexpected consequences." ) msg.setWindowTitle("Restart Failed Realizations") msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) result = msg.exec_() if result == QMessageBox.Ok: self.restart_button.setVisible(False) self.kill_button.setVisible(True) self.done_button.setVisible(False) active_realizations = self.create_mask_from_failed_realizations() self._simulations_argments[ 'active_realizations'] = active_realizations self._simulations_argments[ 'prev_successful_realizations'] = self._simulations_argments.get( 'prev_successful_realizations', 0) self._simulations_argments[ 'prev_successful_realizations'] += self.count_successful_realizations( ) self.startSimulation() def toggle_detailed_progress(self): self.detailed_progress.setVisible(not ( self.detailed_progress.isVisible())) self.dummy_widget_container.setVisible(not ( self.detailed_progress.isVisible())) self.adjustSize()
class QtPluginDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.installer = Installer() self.setup_ui() self.installer.set_output_widget(self.stdout_text) self.installer.process.started.connect(self._on_installer_start) self.installer.process.finished.connect(self._on_installer_done) self.refresh() def _on_installer_start(self): self.show_status_btn.setChecked(True) self.working_indicator.show() self.process_error_indicator.hide() def _on_installer_done(self, exit_code, exit_status): self.working_indicator.hide() if exit_code: self.process_error_indicator.show() else: self.show_status_btn.setChecked(False) self.refresh() self.plugin_sorter.refresh() def refresh(self): self.installed_list.clear() self.available_list.clear() # fetch installed from ...plugins import plugin_manager plugin_manager.discover() # since they might not be loaded yet already_installed = set() for plugin_name, mod_name, distname in plugin_manager.iter_available(): # not showing these in the plugin dialog if plugin_name in ('napari_plugin_engine', ): continue if distname: already_installed.add(distname) meta = standard_metadata(distname) else: meta = {} self.installed_list.addItem( ProjectInfo( normalized_name(distname or ''), meta.get('version', ''), meta.get('url', ''), meta.get('summary', ''), meta.get('author', ''), meta.get('license', ''), ), plugin_name=plugin_name, enabled=plugin_name in plugin_manager.plugins, ) # self.v_splitter.setSizes([70 * self.installed_list.count(), 10, 10]) # fetch available plugins self.worker = create_worker(iter_napari_plugin_info) def _handle_yield(project_info): if project_info.name in already_installed: self.installed_list.tag_outdated(project_info) else: self.available_list.addItem(project_info) self.worker.yielded.connect(_handle_yield) self.worker.finished.connect(self.working_indicator.hide) self.worker.finished.connect(self._update_count_in_label) self.worker.start() def setup_ui(self): self.resize(1080, 640) vlay_1 = QVBoxLayout(self) self.h_splitter = QSplitter(self) vlay_1.addWidget(self.h_splitter) self.h_splitter.setOrientation(Qt.Horizontal) self.v_splitter = QSplitter(self.h_splitter) self.v_splitter.setOrientation(Qt.Vertical) self.v_splitter.setMinimumWidth(500) self.plugin_sorter = QtPluginSorter(parent=self.h_splitter) self.plugin_sorter.layout().setContentsMargins(2, 0, 0, 0) self.plugin_sorter.hide() installed = QWidget(self.v_splitter) lay = QVBoxLayout(installed) lay.setContentsMargins(0, 2, 0, 2) lay.addWidget(QLabel(trans._("Installed Plugins"))) self.installed_list = QPluginList(installed, self.installer) lay.addWidget(self.installed_list) uninstalled = QWidget(self.v_splitter) lay = QVBoxLayout(uninstalled) lay.setContentsMargins(0, 2, 0, 2) self.avail_label = QLabel(trans._("Available Plugins")) lay.addWidget(self.avail_label) self.available_list = QPluginList(uninstalled, self.installer) lay.addWidget(self.available_list) self.stdout_text = QTextEdit(self.v_splitter) self.stdout_text.setReadOnly(True) self.stdout_text.setObjectName("pip_install_status") self.stdout_text.hide() buttonBox = QHBoxLayout() self.working_indicator = QLabel(trans._("loading ..."), self) sp = self.working_indicator.sizePolicy() sp.setRetainSizeWhenHidden(True) self.working_indicator.setSizePolicy(sp) self.process_error_indicator = QLabel(self) self.process_error_indicator.setObjectName("error_label") self.process_error_indicator.hide() load_gif = str(Path(napari.resources.__file__).parent / "loading.gif") mov = QMovie(load_gif) mov.setScaledSize(QSize(18, 18)) self.working_indicator.setMovie(mov) mov.start() self.direct_entry_edit = QLineEdit(self) self.direct_entry_edit.installEventFilter(self) self.direct_entry_edit.setPlaceholderText( trans._('install by name/url, or drop file...')) self.direct_entry_btn = QPushButton(trans._("Install"), self) self.direct_entry_btn.clicked.connect(self._install_packages) self.show_status_btn = QPushButton(trans._("Show Status"), self) self.show_status_btn.setFixedWidth(100) self.show_sorter_btn = QPushButton(trans._("<< Show Sorter"), self) self.close_btn = QPushButton(trans._("Close"), self) self.close_btn.clicked.connect(self.reject) buttonBox.addWidget(self.show_status_btn) buttonBox.addWidget(self.working_indicator) buttonBox.addWidget(self.direct_entry_edit) buttonBox.addWidget(self.direct_entry_btn) buttonBox.addWidget(self.process_error_indicator) buttonBox.addSpacing(60) buttonBox.addWidget(self.show_sorter_btn) buttonBox.addWidget(self.close_btn) buttonBox.setContentsMargins(0, 0, 4, 0) vlay_1.addLayout(buttonBox) self.show_status_btn.setCheckable(True) self.show_status_btn.setChecked(False) self.show_status_btn.toggled.connect(self._toggle_status) self.show_sorter_btn.setCheckable(True) self.show_sorter_btn.setChecked(False) self.show_sorter_btn.toggled.connect(self._toggle_sorter) self.v_splitter.setStretchFactor(1, 2) self.h_splitter.setStretchFactor(0, 2) def _update_count_in_label(self): count = self.available_list.count() self.avail_label.setText( trans._("Available Plugins ({count})".format(count=count))) def eventFilter(self, watched, event): if event.type() == QEvent.DragEnter: # we need to accept this event explicitly to be able # to receive QDropEvents! event.accept() if event.type() == QEvent.Drop: md = event.mimeData() if md.hasUrls(): files = [url.toLocalFile() for url in md.urls()] self.direct_entry_edit.setText(files[0]) return True return super().eventFilter(watched, event) def _toggle_sorter(self, show): if show: self.show_sorter_btn.setText(trans._(">> Hide Sorter")) self.plugin_sorter.show() else: self.show_sorter_btn.setText(trans._("<< Show Sorter")) self.plugin_sorter.hide() def _toggle_status(self, show): if show: self.show_status_btn.setText(trans._("Hide Status")) self.stdout_text.show() else: self.show_status_btn.setText(trans._("Show Status")) self.stdout_text.hide() def _install_packages(self, packages: Sequence[str] = ()): if not packages: _packages = self.direct_entry_edit.text() if os.path.exists(_packages): packages = [_packages] else: packages = _packages.split() self.direct_entry_edit.clear() if packages: self.installer.install(packages)
def get_auto_correction_widget(self, parent): """.""" auto_wid = QWidget(parent) vbl2 = QVBoxLayout(auto_wid) tabw = QTabWidget(auto_wid) vbl2.addWidget(tabw) # Add Main Tab gpbx = QWidget(tabw) gpbx_lay = QVBoxLayout(gpbx) tabw.addTab(gpbx, 'Main') fbl = QFormLayout() fbl.setHorizontalSpacing(9) gpbx_lay.addLayout(fbl) lbl = QLabel('Freq. [Hz]', gpbx) wid = self.create_pair(gpbx, 'LoopFreq') fbl.addRow(lbl, wid) lbl = QLabel('Max. Orb. Distortion [um]', gpbx) wid = self.create_pair(gpbx, 'LoopMaxOrbDistortion') fbl.addRow(lbl, wid) wid = QWidget(gpbx) wid.setLayout(QHBoxLayout()) pbtn = QPushButton('Loop Performance', wid) pbtn.setIcon(qta.icon('mdi.poll')) wid.layout().addStretch() wid.layout().addWidget(pbtn) icon = qta.icon( 'fa5s.hammer', color=_util.get_appropriate_color(self.acc)) wind = create_window_from_widget( PerformanceWidget, title='Loop Performance', icon=icon) _util.connect_window( pbtn, wind, self, device=self.device, prefix=self.prefix) fbl.addRow(wid) # Add PID Tab gpbx = QWidget(tabw) gpbx_lay = QGridLayout(gpbx) gpbx_lay.setSpacing(1) tabw.addTab(gpbx, 'PID') gpbx_lay.addWidget(QLabel('CH', gpbx), 1, 0) gpbx_lay.addWidget(QLabel('CV', gpbx), 2, 0) tmpl = 'LoopPID{:s}{:s}' pairs = [] for i, k in enumerate(('Kp', 'Ki', 'Kd'), 1): gpbx_lay.addWidget( QLabel(k, gpbx), 0, i, alignment=Qt.AlignCenter) pair = self.create_pair(wid, tmpl.format(k, 'CH'), is_vert=True) pairs.append(pair) gpbx_lay.addWidget(pair, 1, i) pair = self.create_pair(wid, tmpl.format(k, 'CV'), is_vert=True) pairs.append(pair) gpbx_lay.addWidget(pair, 2, i) if self.acc in {'SI', 'BO'}: pair = self.create_pair( wid, tmpl.format(k, 'RF'), is_vert=True) pairs.append(pair) gpbx_lay.addWidget(pair, 3, i) if self.acc in {'SI', 'BO'}: gpbx_lay.addWidget(QLabel('RF', gpbx), 3, 0) pbc = QPushButton('SP') pbc.setStyleSheet('max-width:2.2em;') gpbx_lay.addWidget(pbc, 0, 0) pbc.setCheckable(True) pbc.setChecked(False) pbc.toggled.connect( lambda x: pbc.setText('RB' if x else 'SP')) for pair in pairs: pair.rb_wid.setVisible(False) pbc.toggled.connect(pair.rb_wid.setVisible) pbc.toggled.connect(pair.sp_wid.setHidden) gpbx_lay.setRowStretch(4, 2) return auto_wid
class PlotNameWidget(QWidget): """A widget to display the plot name, and edit and close buttons This widget is added to the table widget to support the renaming and close buttons, as well as the direct renaming functionality. """ def __init__(self, presenter, plot_number, parent=None): super(PlotNameWidget, self).__init__(parent) self.presenter = presenter self.plot_number = plot_number self.mutex = QMutex() self.line_edit = QLineEdit( self.presenter.get_plot_name_from_number(plot_number)) self.line_edit.setReadOnly(True) self.line_edit.setFrame(False) # changes the line edit to look like normal even when self.line_edit.setStyleSheet( """* { background-color: rgba(0, 0, 0, 0); } QLineEdit:disabled { color: black; }""") self.line_edit.setAttribute(Qt.WA_TransparentForMouseEvents, True) self.line_edit.editingFinished.connect(self.rename_plot) # Disabling the line edit prevents it from temporarily # grabbing focus when changing code editors - this triggered # the editingFinished signal, which was causing #26305 self.line_edit.setDisabled(True) shown_icon = get_icon('mdi.eye') self.hide_button = QPushButton(shown_icon, "") self.hide_button.setToolTip('Hide') self.hide_button.setFlat(True) self.hide_button.setMaximumWidth(self.hide_button.iconSize().width() * 5 / 3) self.hide_button.clicked.connect(self.toggle_visibility) rename_icon = get_icon('mdi.square-edit-outline') self.rename_button = QPushButton(rename_icon, "") self.rename_button.setToolTip('Rename') self.rename_button.setFlat(True) self.rename_button.setMaximumWidth( self.rename_button.iconSize().width() * 5 / 3) self.rename_button.setCheckable(True) self.rename_button.toggled.connect(self.rename_button_toggled) close_icon = get_icon('mdi.close') self.close_button = QPushButton(close_icon, "") self.close_button.setToolTip('Delete') self.close_button.setFlat(True) self.close_button.setMaximumWidth( self.close_button.iconSize().width() * 5 / 3) self.close_button.clicked.connect( lambda: self.close_pressed(self.plot_number)) self.layout = QHBoxLayout() # Get rid of the top and bottom margins - the button provides # some natural margin anyway. Get rid of right margin and # reduce spacing to get buttons closer together. self.layout.setContentsMargins(5, 0, 0, 0) self.layout.setSpacing(0) self.layout.addWidget(self.line_edit) self.layout.addWidget(self.hide_button) self.layout.addWidget(self.rename_button) self.layout.addWidget(self.close_button) self.layout.sizeHint() self.setLayout(self.layout) def set_plot_name(self, new_name): """ Sets the internally stored and displayed plot name :param new_name: The name to set """ self.line_edit.setText(new_name) def close_pressed(self, plot_number): """ Close the plot with the given name :param plot_number: The unique number in GlobalFigureManager """ self.presenter.close_single_plot(plot_number) def rename_button_toggled(self, checked): """ If the rename button is pressed from being unchecked then make the line edit item editable :param checked: True if the rename toggle is now pressed """ if checked: self.toggle_plot_name_editable(True, toggle_rename_button=False) def toggle_plot_name_editable(self, editable, toggle_rename_button=True): """ Set the line edit item to be editable or not editable. If editable move the cursor focus to the editable name and highlight it all. :param editable: If true make the plot name editable, else make it read only :param toggle_rename_button: If true also toggle the rename button state """ self.line_edit.setReadOnly(not editable) self.line_edit.setDisabled(not editable) self.line_edit.setAttribute(Qt.WA_TransparentForMouseEvents, not editable) # This is a sneaky way to avoid the issue of two calls to # this toggle method, by effectively disabling the button # press in edit mode. self.rename_button.setAttribute(Qt.WA_TransparentForMouseEvents, editable) if toggle_rename_button: self.rename_button.setChecked(editable) if editable: self.line_edit.setFocus() self.line_edit.selectAll() else: self.line_edit.setSelection(0, 0) def toggle_visibility(self): """ Calls the presenter to hide the selected plot """ self.presenter.toggle_plot_visibility(self.plot_number) def set_visibility_icon(self, is_shown): """ Change the widget icon between shown and hidden :param is_shown: True if plot is shown, false if hidden """ if is_shown: self.hide_button.setIcon(get_icon('mdi.eye')) self.hide_button.setToolTip('Hide') else: self.hide_button.setIcon(get_icon('mdi.eye', 'lightgrey')) self.hide_button.setToolTip('Show') def rename_plot(self): """ Called when the editing is finished, gets the presenter to do the real renaming of the plot """ self.presenter.rename_figure(self.plot_number, self.line_edit.text()) self.toggle_plot_name_editable(False)
class QtPluginDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.refresh_state = RefreshState.DONE self.already_installed = set() installer_type = "mamba" if running_as_constructor_app() else "pip" self.installer = Installer(installer=installer_type) self.setup_ui() self.installer.set_output_widget(self.stdout_text) self.installer.started.connect(self._on_installer_start) self.installer.finished.connect(self._on_installer_done) self.refresh() def _on_installer_start(self): self.cancel_all_btn.setVisible(True) self.working_indicator.show() self.process_error_indicator.hide() self.close_btn.setDisabled(True) def _on_installer_done(self, exit_code): self.working_indicator.hide() if exit_code: self.process_error_indicator.show() self.cancel_all_btn.setVisible(False) self.close_btn.setDisabled(False) self.refresh() def closeEvent(self, event): if self.close_btn.isEnabled(): super().closeEvent(event) event.ignore() def refresh(self): if self.refresh_state != RefreshState.DONE: self.refresh_state = RefreshState.OUTDATED return self.refresh_state = RefreshState.REFRESHING self.installed_list.clear() self.available_list.clear() # fetch installed from npe2 import PluginManager from ...plugins import plugin_manager plugin_manager.discover() # since they might not be loaded yet self.already_installed = set() def _add_to_installed(distname, enabled, npe_version=1): norm_name = normalized_name(distname or '') if distname: try: meta = metadata(distname) except PackageNotFoundError: self.refresh_state = RefreshState.OUTDATED return # a race condition has occurred and the package is uninstalled by another thread if len(meta) == 0: # will not add builtins. return self.already_installed.add(norm_name) else: meta = {} self.installed_list.addItem( PackageMetadata( metadata_version="1.0", name=norm_name, version=meta.get('version', ''), summary=meta.get('summary', ''), home_page=meta.get('url', ''), author=meta.get('author', ''), license=meta.get('license', ''), ), installed=True, enabled=enabled, npe_version=npe_version, ) pm2 = PluginManager.instance() for manifest in pm2.iter_manifests(): distname = normalized_name(manifest.name or '') if distname in self.already_installed or distname == 'napari': continue enabled = not pm2.is_disabled(manifest.name) _add_to_installed(distname, enabled, npe_version=2) for ( plugin_name, _mod_name, distname, ) in plugin_manager.iter_available(): # not showing these in the plugin dialog if plugin_name in ('napari_plugin_engine', ): continue if distname in self.already_installed: continue _add_to_installed(distname, not plugin_manager.is_blocked(plugin_name)) self.installed_label.setText( trans._( "Installed Plugins ({amount})", amount=len(self.already_installed), )) # fetch available plugins settings = get_settings() use_hub = (running_as_bundled_app() or running_as_constructor_app() or settings.plugins.plugin_api.name == "napari_hub") if use_hub: conda_forge = running_as_constructor_app() self.worker = create_worker(iter_hub_plugin_info, conda_forge=conda_forge) else: self.worker = create_worker(iter_napari_plugin_info) self.worker.yielded.connect(self._handle_yield) self.worker.finished.connect(self.working_indicator.hide) self.worker.finished.connect(self._update_count_in_label) self.worker.finished.connect(self._end_refresh) self.worker.start() def setup_ui(self): self.resize(1080, 640) vlay_1 = QVBoxLayout(self) self.h_splitter = QSplitter(self) vlay_1.addWidget(self.h_splitter) self.h_splitter.setOrientation(Qt.Horizontal) self.v_splitter = QSplitter(self.h_splitter) self.v_splitter.setOrientation(Qt.Vertical) self.v_splitter.setMinimumWidth(500) installed = QWidget(self.v_splitter) lay = QVBoxLayout(installed) lay.setContentsMargins(0, 2, 0, 2) self.installed_label = QLabel(trans._("Installed Plugins")) self.packages_filter = QLineEdit() self.packages_filter.setPlaceholderText(trans._("filter...")) self.packages_filter.setMaximumWidth(350) self.packages_filter.setClearButtonEnabled(True) mid_layout = QVBoxLayout() mid_layout.addWidget(self.packages_filter) mid_layout.addWidget(self.installed_label) lay.addLayout(mid_layout) self.installed_list = QPluginList(installed, self.installer) self.packages_filter.textChanged.connect(self.installed_list.filter) lay.addWidget(self.installed_list) uninstalled = QWidget(self.v_splitter) lay = QVBoxLayout(uninstalled) lay.setContentsMargins(0, 2, 0, 2) self.avail_label = QLabel(trans._("Available Plugins")) mid_layout = QHBoxLayout() mid_layout.addWidget(self.avail_label) mid_layout.addStretch() lay.addLayout(mid_layout) self.available_list = QPluginList(uninstalled, self.installer) self.packages_filter.textChanged.connect(self.available_list.filter) lay.addWidget(self.available_list) self.stdout_text = QTextEdit(self.v_splitter) self.stdout_text.setReadOnly(True) self.stdout_text.setObjectName("pip_install_status") self.stdout_text.hide() buttonBox = QHBoxLayout() self.working_indicator = QLabel(trans._("loading ..."), self) sp = self.working_indicator.sizePolicy() sp.setRetainSizeWhenHidden(True) self.working_indicator.setSizePolicy(sp) self.process_error_indicator = QLabel(self) self.process_error_indicator.setObjectName("error_label") self.process_error_indicator.hide() load_gif = str(Path(napari.resources.__file__).parent / "loading.gif") mov = QMovie(load_gif) mov.setScaledSize(QSize(18, 18)) self.working_indicator.setMovie(mov) mov.start() visibility_direct_entry = not running_as_constructor_app() self.direct_entry_edit = QLineEdit(self) self.direct_entry_edit.installEventFilter(self) self.direct_entry_edit.setPlaceholderText( trans._('install by name/url, or drop file...')) self.direct_entry_edit.setVisible(visibility_direct_entry) self.direct_entry_btn = QPushButton(trans._("Install"), self) self.direct_entry_btn.setVisible(visibility_direct_entry) self.direct_entry_btn.clicked.connect(self._install_packages) self.show_status_btn = QPushButton(trans._("Show Status"), self) self.show_status_btn.setFixedWidth(100) self.cancel_all_btn = QPushButton(trans._("cancel all actions"), self) self.cancel_all_btn.setObjectName("remove_button") self.cancel_all_btn.setVisible(False) self.cancel_all_btn.clicked.connect(lambda: self.installer.cancel()) self.close_btn = QPushButton(trans._("Close"), self) self.close_btn.clicked.connect(self.accept) self.close_btn.setObjectName("close_button") buttonBox.addWidget(self.show_status_btn) buttonBox.addWidget(self.working_indicator) buttonBox.addWidget(self.direct_entry_edit) buttonBox.addWidget(self.direct_entry_btn) if not visibility_direct_entry: buttonBox.addStretch() buttonBox.addWidget(self.process_error_indicator) buttonBox.addSpacing(20) buttonBox.addWidget(self.cancel_all_btn) buttonBox.addSpacing(20) buttonBox.addWidget(self.close_btn) buttonBox.setContentsMargins(0, 0, 4, 0) vlay_1.addLayout(buttonBox) self.show_status_btn.setCheckable(True) self.show_status_btn.setChecked(False) self.show_status_btn.toggled.connect(self._toggle_status) self.v_splitter.setStretchFactor(1, 2) self.h_splitter.setStretchFactor(0, 2) self.packages_filter.setFocus() def _update_count_in_label(self): count = self.available_list.count() self.avail_label.setText( trans._("Available Plugins ({count})", count=count)) def _end_refresh(self): refresh_state = self.refresh_state self.refresh_state = RefreshState.DONE if refresh_state == RefreshState.OUTDATED: self.refresh() def eventFilter(self, watched, event): if event.type() == QEvent.DragEnter: # we need to accept this event explicitly to be able # to receive QDropEvents! event.accept() if event.type() == QEvent.Drop: md = event.mimeData() if md.hasUrls(): files = [url.toLocalFile() for url in md.urls()] self.direct_entry_edit.setText(files[0]) return True return super().eventFilter(watched, event) def _toggle_status(self, show): if show: self.show_status_btn.setText(trans._("Hide Status")) self.stdout_text.show() else: self.show_status_btn.setText(trans._("Show Status")) self.stdout_text.hide() def _install_packages(self, packages: Sequence[str] = ()): if not packages: _packages = self.direct_entry_edit.text() if os.path.exists(_packages): packages = [_packages] else: packages = _packages.split() self.direct_entry_edit.clear() if packages: self.installer.install(packages) def _handle_yield(self, data: Tuple[PackageMetadata, bool]): project_info, is_available = data if project_info.name in self.already_installed: self.installed_list.tag_outdated(project_info, is_available) else: self.available_list.addItem(project_info) if not is_available: self.available_list.tag_unavailable(project_info) self.filter() def filter(self, text: str = None) -> None: """Filter by text or set current text as filter.""" if text is None: text = self.packages_filter.text() else: self.packages_filter.setText(text) self.installed_list.filter(text) self.available_list.filter(text)
class Dimension(QWidget): stateChanged = Signal(int) valueChanged = Signal() """ pass in dimension state: one of (State.X, State.Y, State.NONE, State.DISBALE) Can be run independently by: from mantidqt.widgets.sliceviewer.dimensionwidget import Dimension from qtpy.QtWidgets import QApplication app = QApplication([]) window = Dimension({'minimum':-1.1, 'number_of_bins':11, 'width':0.2, 'name':'Dim0', 'units':'A'}) window.show() app.exec_() """ def __init__(self, dim_info, number=0, state=State.NONE, parent=None): super(Dimension, self).__init__(parent) self.minimum = dim_info['minimum'] self.nbins = dim_info['number_of_bins'] self.width = dim_info['width'] self.number = number self.layout = QHBoxLayout(self) self.name = QLabel(dim_info['name']) self.units = QLabel(dim_info['units']) self.x = QPushButton('X') self.x.setFixedSize(32,32) self.x.setCheckable(True) self.x.clicked.connect(self.x_clicked) self.y = QPushButton('Y') self.y.setFixedSize(32,32) self.y.setCheckable(True) self.y.clicked.connect(self.y_clicked) self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.nbins-1) self.slider.valueChanged.connect(self.slider_changed) self.spinbox = QDoubleSpinBox() self.spinbox.setDecimals(3) self.spinbox.setRange(self.get_bin_center(0), self.get_bin_center(self.nbins-1)) self.spinbox.setSingleStep(self.width) self.spinbox.valueChanged.connect(self.spinbox_changed) self.layout.addWidget(self.name) self.layout.addWidget(self.x) self.layout.addWidget(self.y) self.layout.addWidget(self.slider, stretch=1) self.layout.addStretch(0) self.layout.addWidget(self.spinbox) self.layout.addWidget(self.units) self.set_value(0) if self.nbins < 2: state = State.DISABLE self.set_state(state) def set_state(self, state): self.state = state if self.state == State.X: self.x.setChecked(True) self.y.setChecked(False) self.slider.hide() self.spinbox.hide() self.units.hide() elif self.state == State.Y: self.x.setChecked(False) self.y.setChecked(True) self.slider.hide() self.spinbox.hide() self.units.hide() elif self.state == State.NONE: self.x.setChecked(False) self.y.setChecked(False) self.slider.show() self.spinbox.show() self.units.show() else: self.x.setChecked(False) self.x.setDisabled(True) self.y.setChecked(False) self.y.setDisabled(True) self.slider.hide() self.spinbox.show() self.spinbox.setDisabled(True) self.units.show() def get_state(self): return self.state def x_clicked(self): old_state = self.state self.set_state(State.X) if self.state != old_state: self.stateChanged.emit(self.number) def y_clicked(self): old_state = self.state self.set_state(State.Y) if self.state != old_state: self.stateChanged.emit(self.number) def spinbox_changed(self): self.value = self.spinbox.value() self.update_slider() self.valueChanged.emit() def slider_changed(self): self.value = self.get_bin_center(self.slider.value()) self.update_spinbox() self.valueChanged.emit() def get_bin_center(self, n): return (n+0.5)*self.width+self.minimum def update_slider(self): i = (self.value-self.minimum)/self.width self.slider.setValue(int(min(max(i, 0), self.nbins-1))) def update_spinbox(self): self.spinbox.setValue(self.value) def set_value(self, value): self.value = value self.update_slider() self.update_spinbox() def get_value(self): return self.value
class LibraryWidget(QWidget): sigImageChanged = Signal() def __init__(self): super(LibraryWidget, self).__init__() self.image_items = [] self.views = [] self.current_image_item = None self.setLayout(QHBoxLayout()) self.right_layout = QVBoxLayout() self.scroll_widget = QScrollArea() self.scroll_widget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll_widget.setWidgetResizable(True) self.flow_widget = QWidget() self.flow_layout = FlowLayout() self.flow_widget.setLayout(self.flow_layout) self.scroll_widget.setWidget(self.flow_widget) self.layout().addWidget(self.scroll_widget) self.hist_widget = HistogramLUTWidget() self.hist_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.hist_widget.item.sigLevelChangeFinished.connect(self.set_levels) self.hist_widget.item.sigLookupTableChanged.connect( self.set_lookup_table) self.layout().addLayout(self.right_layout) self.right_layout.addWidget(self.hist_widget) self.link_button = QPushButton("Link Axes") self.link_button.setCheckable(True) self.right_layout.addWidget(self.link_button) # TODO: use Qt styling pallet for theming # self.setStyleSheet("background-color:#000;") self.current_view = None self.axes_linked = False def set_slice(self, *args, **kwargs): # TODO: support generic orthogonal slicing print('slice:', args, kwargs) def set_levels(self, *args, **kwargs): levels = self.hist_widget.item.getLevels() for image_item in self.image_items: image_item.setLevels(levels) def set_lookup_table(self, *args, **kwargs): if self.current_image_item and self.current_image_item.image is not None: lut = self.hist_widget.item.getLookupTable( self.current_image_item.image) for image_item in self.image_items: image_item.setLookupTable(lut) def set_current_imageitem(self, imageitem: ImageItem): if self.current_image_item: self.current_image_item.deactivate() self.current_image_item = imageitem self.current_view = imageitem.getViewBox() self.current_image_item.activate() self.hist_widget.item.setImageItem(self.current_image_item) def propagate_axes(self): if self.link_button.isChecked(): view = self.sender() view_rect = view.viewRect() for other_view in self.views: if other_view is not view: with QSignalBlocker(other_view): other_view.setRange(rect=view_rect, padding=0) def add_image(self, image, label): w = QFrame() w.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) w.setLineWidth(2) w.setFixedSize(QSize(500, 500)) w.setLayout(QVBoxLayout()) gv = ScrollableGraphicsLayoutWidget() vb = ViewBox(lockAspect=True) ii = ActivatableImageItem(image=image) ii.sigActivated.connect(self.set_current_imageitem) self.hist_widget.item.setImageItem(ii) self.current_image_item = ii self.image_items.append(ii) self.views.append(vb) vb.sigRangeChangedManually.connect(self.propagate_axes) vb.addItem(ii) gv.addItem(vb) self.set_current_imageitem(ii) w.layout().addWidget(gv) l = QLabel(label) # l.setStyleSheet("color: white;") w.layout().addWidget(l) self.flow_layout.addWidget(w) self.last_vb = vb def update_image(self, index, image, label): if index < len(self.image_items): self.image_items[index].setImage(image) else: self.add_image(image, label)
class RunDialog(QDialog): simulation_done = Signal(bool, str) simulation_termination_request = Signal() def __init__(self, config_file, run_model, parent=None): QDialog.__init__(self, parent) self.setWindowFlags(Qt.Window) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.setModal(True) self.setWindowModality(Qt.WindowModal) self.setWindowTitle(f"Simulations - {config_file}") self._snapshot_model = SnapshotModel(self) self._run_model = run_model self._isDetailedDialog = False self._minimum_width = 1200 ert = None if isinstance(run_model, BaseRunModel): ert = run_model.ert() self._ticker = QTimer(self) self._ticker.timeout.connect(self._on_ticker) progress_proxy_model = ProgressProxyModel(self._snapshot_model, parent=self) self._total_progress_label = QLabel( _TOTAL_PROGRESS_TEMPLATE.format( total_progress=0, phase_name=run_model.getPhaseName()), self, ) self._total_progress_bar = QProgressBar(self) self._total_progress_bar.setRange(0, 100) self._total_progress_bar.setTextVisible(False) self._iteration_progress_label = QLabel(self) self._progress_view = ProgressView(self) self._progress_view.setModel(progress_proxy_model) self._progress_view.setIndeterminate(True) legend_view = LegendView(self) legend_view.setModel(progress_proxy_model) self._tab_widget = QTabWidget(self) self._tab_widget.currentChanged.connect(self._current_tab_changed) self._snapshot_model.rowsInserted.connect(self.on_new_iteration) self._job_label = QLabel(self) self._job_model = JobListProxyModel(self, 0, 0, 0, 0) self._job_model.setSourceModel(self._snapshot_model) self._job_view = QTableView(self) self._job_view.setVerticalScrollMode(QAbstractItemView.ScrollPerItem) self._job_view.setSelectionBehavior(QAbstractItemView.SelectRows) self._job_view.setSelectionMode(QAbstractItemView.SingleSelection) self._job_view.clicked.connect(self._job_clicked) self._open_files = {} self._job_view.setModel(self._job_model) self.running_time = QLabel("") self.plot_tool = PlotTool(ert, config_file) self.plot_tool.setParent(self) self.plot_button = QPushButton(self.plot_tool.getName()) self.plot_button.clicked.connect(self.plot_tool.trigger) self.plot_button.setEnabled(ert is not None) self.kill_button = QPushButton("Kill simulations") self.done_button = QPushButton("Done") self.done_button.setHidden(True) self.restart_button = QPushButton("Restart") self.restart_button.setHidden(True) self.show_details_button = QPushButton("Show details") self.show_details_button.setCheckable(True) size = 20 spin_movie = resourceMovie("loading.gif") spin_movie.setSpeed(60) spin_movie.setScaledSize(QSize(size, size)) spin_movie.start() self.processing_animation = QLabel() self.processing_animation.setMaximumSize(QSize(size, size)) self.processing_animation.setMinimumSize(QSize(size, size)) self.processing_animation.setMovie(spin_movie) button_layout = QHBoxLayout() button_layout.addWidget(self.processing_animation) button_layout.addWidget(self.running_time) button_layout.addStretch() button_layout.addWidget(self.show_details_button) button_layout.addWidget(self.plot_button) button_layout.addWidget(self.kill_button) button_layout.addWidget(self.done_button) button_layout.addWidget(self.restart_button) button_widget_container = QWidget() button_widget_container.setLayout(button_layout) layout = QVBoxLayout() layout.addWidget(self._total_progress_label) layout.addWidget(self._total_progress_bar) layout.addWidget(self._iteration_progress_label) layout.addWidget(self._progress_view) layout.addWidget(legend_view) layout.addWidget(self._tab_widget) layout.addWidget(self._job_label) layout.addWidget(self._job_view) layout.addWidget(button_widget_container) self.setLayout(layout) self.kill_button.clicked.connect(self.killJobs) self.done_button.clicked.connect(self.accept) self.restart_button.clicked.connect(self.restart_failed_realizations) self.show_details_button.clicked.connect(self.toggle_detailed_progress) self.simulation_done.connect(self._on_simulation_done) self.setMinimumWidth(self._minimum_width) self._setSimpleDialog() def _current_tab_changed(self, index: int): # Clear the selection in the other tabs for i in range(0, self._tab_widget.count()): if i != index: self._tab_widget.widget(i).clearSelection() def _setSimpleDialog(self) -> None: self._isDetailedDialog = False self._tab_widget.setVisible(False) self._job_label.setVisible(False) self._job_view.setVisible(False) self.show_details_button.setText("Show details") def _setDetailedDialog(self) -> None: self._isDetailedDialog = True self._tab_widget.setVisible(True) self._job_label.setVisible(True) self._job_view.setVisible(True) self.show_details_button.setText("Hide details") @Slot(QModelIndex, int, int) def on_new_iteration(self, parent: QModelIndex, start: int, end: int) -> None: if not parent.isValid(): index = self._snapshot_model.index(start, 0, parent) iter_row = start self._iteration_progress_label.setText( f"Progress for iteration {index.internalPointer().id}") widget = RealizationWidget(iter_row) widget.setSnapshotModel(self._snapshot_model) widget.currentChanged.connect(self._select_real) self._tab_widget.addTab( widget, f"Realizations for iteration {index.internalPointer().id}") @Slot(QModelIndex) def _job_clicked(self, index): if not index.isValid(): return selected_file = index.data(FileRole) if selected_file and selected_file not in self._open_files: job_name = index.siblingAtColumn(0).data() viewer = FileDialog( selected_file, job_name, index.row(), index.model().get_real(), index.model().get_iter(), self, ) self._open_files[selected_file] = viewer def remove_file(): """ We have sometimes seen this fail because the selected file is not in open file, without being able to reproduce the exception. """ try: self._open_files.pop(selected_file) except KeyError: logger = logging.getLogger(__name__) logger.exception( f"Failed to pop: {selected_file} from {self._open_files}" ) viewer.finished.connect(remove_file) elif selected_file in self._open_files: self._open_files[selected_file].raise_() @Slot(QModelIndex) def _select_real(self, index): step = 0 stage = 0 real = index.row() iter_ = index.model().get_iter() self._job_model.set_step(iter_, real, stage, step) self._job_label.setText( f"Realization id {index.data(RealIens)} in iteration {iter_}") self._job_view.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) def reject(self): return def closeEvent(self, QCloseEvent): if self._run_model.isFinished(): self.simulation_done.emit(self._run_model.hasRunFailed(), self._run_model.getFailMessage()) else: # Kill jobs if dialog is closed if self.killJobs() != QMessageBox.Yes: QCloseEvent.ignore() def startSimulation(self): self._run_model.reset() self._snapshot_model.reset() self._tab_widget.clear() evaluator_server_config = EvaluatorServerConfig() def run(): asyncio.set_event_loop(asyncio.new_event_loop()) self._run_model.startSimulations( evaluator_server_config=evaluator_server_config, ) simulation_thread = Thread(name="ert_gui_simulation_thread") simulation_thread.setDaemon(True) simulation_thread.run = run simulation_thread.start() self._ticker.start(1000) tracker = EvaluatorTracker( self._run_model, ee_con_info=evaluator_server_config.get_connection_info(), ) worker = TrackerWorker(tracker) worker_thread = QThread() worker.done.connect(worker_thread.quit) worker.consumed_event.connect(self._on_tracker_event) worker.moveToThread(worker_thread) self.simulation_done.connect(worker.stop) self._worker = worker self._worker_thread = worker_thread worker_thread.started.connect(worker.consume_and_emit) self._worker_thread.start() def killJobs(self): msg = "Are you sure you want to kill the currently running simulations?" kill_job = QMessageBox.question(self, "Kill simulations?", msg, QMessageBox.Yes | QMessageBox.No) if kill_job == QMessageBox.Yes: # Normally this slot would be invoked by the signal/slot system, # but the worker is busy tracking the evaluation. self._worker.request_termination() self.reject() return kill_job @Slot(bool, str) def _on_simulation_done(self, failed, failed_msg): self.processing_animation.hide() self.kill_button.setHidden(True) self.done_button.setHidden(False) self.restart_button.setVisible( self._run_model.has_failed_realizations()) self.restart_button.setEnabled(self._run_model.support_restart) self._total_progress_bar.setValue(100) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format( total_progress=100, phase_name=self._run_model.getPhaseName())) if failed: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Simulations failed!".center(100)) msg.setDetailedText(failed_msg) msg.exec_() @Slot() def _on_ticker(self): runtime = self._run_model.get_runtime() self.running_time.setText(format_running_time(runtime)) @Slot(object) def _on_tracker_event(self, event): if isinstance(event, EndEvent): self.simulation_done.emit(event.failed, event.failed_msg) self._worker.stop() self._ticker.stop() elif isinstance(event, FullSnapshotEvent): if event.snapshot is not None: self._snapshot_model._add_snapshot(event.snapshot, event.iteration) self._progress_view.setIndeterminate(event.indeterminate) progress = int(event.progress * 100) self._total_progress_bar.setValue(progress) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format(total_progress=progress, phase_name=event.phase_name)) elif isinstance(event, SnapshotUpdateEvent): if event.partial_snapshot is not None: self._snapshot_model._add_partial_snapshot( event.partial_snapshot, event.iteration) self._progress_view.setIndeterminate(event.indeterminate) progress = int(event.progress * 100) self._total_progress_bar.setValue(progress) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format(total_progress=progress, phase_name=event.phase_name)) def restart_failed_realizations(self): msg = QMessageBox(self) msg.setIcon(QMessageBox.Information) msg.setText( "Note that workflows will only be executed on the restarted " "realizations and that this might have unexpected consequences.") msg.setWindowTitle("Restart failed realizations") msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) result = msg.exec_() if result == QMessageBox.Ok: self.restart_button.setVisible(False) self.kill_button.setVisible(True) self.done_button.setVisible(False) self._run_model.restart() self.startSimulation() @Slot() def toggle_detailed_progress(self): if self._isDetailedDialog: self._setSimpleDialog() else: self._setDetailedDialog() self.adjustSize()
class PlotNameWidget(QWidget): """A widget to display the plot name, and edit and close buttons This widget is added to the table widget to support the renaming and close buttons, as well as the direct renaming functionality. """ def __init__(self, presenter, plot_number, parent=None): super(PlotNameWidget, self).__init__(parent) self.presenter = presenter self.plot_number = plot_number self.mutex = QMutex() self.line_edit = QLineEdit(self.presenter.get_plot_name_from_number(plot_number)) self.line_edit.setReadOnly(True) self.line_edit.setFrame(False) self.line_edit.setStyleSheet("* { background-color: rgba(0, 0, 0, 0); }") self.line_edit.setAttribute(Qt.WA_TransparentForMouseEvents, True) self.line_edit.editingFinished.connect(self.rename_plot) shown_icon = get_icon('mdi.eye') self.hide_button = QPushButton(shown_icon, "") self.hide_button.setToolTip('Hide') self.hide_button.setFlat(True) self.hide_button.setMaximumWidth(self.hide_button.iconSize().width() * 5 / 3) self.hide_button.clicked.connect(self.toggle_visibility) rename_icon = get_icon('mdi.square-edit-outline') self.rename_button = QPushButton(rename_icon, "") self.rename_button.setToolTip('Rename') self.rename_button.setFlat(True) self.rename_button.setMaximumWidth(self.rename_button.iconSize().width() * 5 / 3) self.rename_button.setCheckable(True) self.rename_button.toggled.connect(self.rename_button_toggled) close_icon = get_icon('mdi.close') self.close_button = QPushButton(close_icon, "") self.close_button.setToolTip('Delete') self.close_button.setFlat(True) self.close_button.setMaximumWidth(self.close_button.iconSize().width() * 5 / 3) self.close_button.clicked.connect(lambda: self.close_pressed(self.plot_number)) self.layout = QHBoxLayout() # Get rid of the top and bottom margins - the button provides # some natural margin anyway. Get rid of right margin and # reduce spacing to get buttons closer together. self.layout.setContentsMargins(5, 0, 0, 0) self.layout.setSpacing(0) self.layout.addWidget(self.line_edit) self.layout.addWidget(self.hide_button) self.layout.addWidget(self.rename_button) self.layout.addWidget(self.close_button) self.layout.sizeHint() self.setLayout(self.layout) def set_plot_name(self, new_name): """ Sets the internally stored and displayed plot name :param new_name: The name to set """ self.line_edit.setText(new_name) def close_pressed(self, plot_number): """ Close the plot with the given name :param plot_number: The unique number in GlobalFigureManager """ self.presenter.close_single_plot(plot_number) def rename_button_toggled(self, checked): """ If the rename button is pressed from being unchecked then make the line edit item editable :param checked: True if the rename toggle is now pressed """ if checked: self.toggle_plot_name_editable(True, toggle_rename_button=False) def toggle_plot_name_editable(self, editable, toggle_rename_button=True): """ Set the line edit item to be editable or not editable. If editable move the cursor focus to the editable name and highlight it all. :param editable: If true make the plot name editable, else make it read only :param toggle_rename_button: If true also toggle the rename button state """ self.line_edit.setReadOnly(not editable) self.line_edit.setAttribute(Qt.WA_TransparentForMouseEvents, not editable) # This is a sneaky way to avoid the issue of two calls to # this toggle method, by effectively disabling the button # press in edit mode. self.rename_button.setAttribute(Qt.WA_TransparentForMouseEvents, editable) if toggle_rename_button: self.rename_button.setChecked(editable) if editable: self.line_edit.setFocus() self.line_edit.selectAll() else: self.line_edit.setSelection(0, 0) def toggle_visibility(self): """ Calls the presenter to hide the selected plot """ self.presenter.toggle_plot_visibility(self.plot_number) def set_visibility_icon(self, is_shown): """ Change the widget icon between shown and hidden :param is_shown: True if plot is shown, false if hidden """ if is_shown: self.hide_button.setIcon(get_icon('mdi.eye')) self.hide_button.setToolTip('Hide') else: self.hide_button.setIcon(get_icon('mdi.eye', 'lightgrey')) self.hide_button.setToolTip('Show') def rename_plot(self): """ Called when the editing is finished, gets the presenter to do the real renaming of the plot """ self.presenter.rename_figure(self.plot_number, self.line_edit.text()) self.toggle_plot_name_editable(False)
class NotificationWidget(QWidget, VCPWidget): def __init__(self, parent=None): super(NotificationWidget, self).__init__(parent) self.notification_channel = getPlugin("notifications") self.main_layout = QVBoxLayout() self.button_layout = QHBoxLayout() self.all_button = QPushButton() self.info_button = QPushButton() self.warn_button = QPushButton() self.error_button = QPushButton() self.debug_button = QPushButton() self.clear_button = QPushButton() self.all_button.setText("all") self.info_button.setText("info") self.warn_button.setText("warn") self.error_button.setText("error") self.debug_button.setText("debug") self.clear_button.setText("clear") self.all_button.setCheckable(True) self.info_button.setCheckable(True) self.warn_button.setCheckable(True) self.error_button.setCheckable(True) self.debug_button.setCheckable(True) self.all_button.setChecked(True) self.info_button.setChecked(False) self.warn_button.setChecked(False) self.error_button.setChecked(False) self.debug_button.setChecked(False) self.clear_button.clicked.connect(self.clear_all_notifications) self.button_layout.addWidget(self.all_button) self.button_layout.addWidget(self.info_button) self.button_layout.addWidget(self.warn_button) self.button_layout.addWidget(self.error_button) self.button_layout.addWidget(self.debug_button) self.button_layout.addWidget(self.clear_button) self.notification_name = QLabel() self.notification_name.setAlignment(Qt.AlignCenter) self.notification_name.setText("All Notifications") self.all_notification_view = QListView() self.all_notification_model = QStandardItemModel( self.all_notification_view) self.all_notification_model_proxy = QSortFilterProxyModel( self.all_notification_view) self.all_notification_model_proxy.setSourceModel( self.all_notification_model) # self.all_notification_view.setModel(self.all_notification_model) self.all_notification_view.setModel(self.all_notification_model_proxy) self.all_notifications = list() self.main_layout.addWidget(self.notification_name) self.main_layout.addWidget(self.all_notification_view) self.main_layout.addLayout(self.button_layout) self.setLayout(self.main_layout) self.notification_channel.info_message.notify(self.on_info_message) self.notification_channel.warn_message.notify(self.on_warn_message) self.notification_channel.error_message.notify(self.on_error_message) self.notification_channel.debug_message.notify(self.on_debug_message) self.all_button.clicked.connect(self.show_all_notifications) self.info_button.clicked.connect(self.show_info_notifications) self.warn_button.clicked.connect(self.show_warn_notifications) self.error_button.clicked.connect(self.show_error_notifications) self.debug_button.clicked.connect(self.show_debug_notifications) def on_info_message(self, message): timestamp = time() dt_object = datetime.fromtimestamp(timestamp) current_time = str(dt_object) msg = 'INFO:\nTIME {}\n {}'.format(current_time, message) notification_item = QStandardItem() notification_item.setText(msg) notification_item.setIcon(QIcon.fromTheme('dialog-information')) notification_item.setEditable(False) self.all_notification_model.appendRow(notification_item) def on_warn_message(self, message): timestamp = time() dt_object = datetime.fromtimestamp(timestamp) current_time = str(dt_object) msg = 'WARNING:\nTIME {}\n {}'.format(current_time, message) notification_item = QStandardItem() notification_item.setText(msg) notification_item.setIcon(QIcon.fromTheme('dialog-warning')) notification_item.setEditable(False) self.all_notification_model.appendRow(notification_item) def on_error_message(self, message): timestamp = time() dt_object = datetime.fromtimestamp(timestamp) current_time = str(dt_object) msg = 'ERROR:\nTIME {}\n {}'.format(current_time, message) notification_item = QStandardItem() notification_item.setText(msg) notification_item.setIcon(QIcon.fromTheme('dialog-error')) notification_item.setEditable(False) self.all_notification_model.appendRow(notification_item) def on_debug_message(self, message): timestamp = time() dt_object = datetime.fromtimestamp(timestamp) current_time = str(dt_object) msg = 'DEBUG\nTIME {}\n {}'.format(current_time, message) notification_item = QStandardItem() notification_item.setText(msg) notification_item.setIcon(QIcon.fromTheme('dialog-question')) notification_item.setEditable(False) self.all_notification_model.appendRow(notification_item) def show_all_notifications(self): self.all_button.setChecked(True) self.info_button.setChecked(False) self.warn_button.setChecked(False) self.error_button.setChecked(False) self.debug_button.setChecked(False) self.notification_name.setText("All Notifications") self.all_notification_model_proxy.setFilterRegExp(None) def show_info_notifications(self): self.all_button.setChecked(False) self.info_button.setChecked(True) self.warn_button.setChecked(False) self.error_button.setChecked(False) self.debug_button.setChecked(False) self.notification_name.setText("Information Notifications") self.all_notification_model_proxy.setFilterRegExp( QRegExp("INFO", Qt.CaseSensitive, QRegExp.FixedString)) def show_warn_notifications(self): self.all_button.setChecked(False) self.info_button.setChecked(False) self.warn_button.setChecked(True) self.error_button.setChecked(False) self.debug_button.setChecked(False) self.notification_name.setText("Warning Notifications") self.all_notification_model_proxy.setFilterRegExp( QRegExp("WANRNING", Qt.CaseSensitive, QRegExp.FixedString)) def show_error_notifications(self): self.all_button.setChecked(False) self.info_button.setChecked(False) self.warn_button.setChecked(False) self.error_button.setChecked(True) self.debug_button.setChecked(False) self.notification_name.setText("Error Notifications") self.all_notification_model_proxy.setFilterRegExp( QRegExp("ERROR", Qt.CaseInsensitive, QRegExp.FixedString)) def show_debug_notifications(self): self.all_button.setChecked(False) self.info_button.setChecked(False) self.warn_button.setChecked(False) self.error_button.setChecked(False) self.debug_button.setChecked(True) self.notification_name.setText("Debug Notifications") self.all_notification_model_proxy.setFilterRegExp( QRegExp("DEBUG", Qt.CaseSensitive, QRegExp.FixedString)) def clear_all_notifications(self): self.all_notification_model.clear()
class AnimateDialog(QDialog): def __init__(self, vpoints: Sequence[VPoint], vlinks: Sequence[VLink], path: _Paths, slider_path: _SliderPaths, monochrome: bool, parent: QWidget): super(AnimateDialog, self).__init__(parent) self.setWindowTitle("Vector Animation") self.setWindowFlags(self.windowFlags() | Qt.WindowMaximizeButtonHint & ~Qt.WindowContextHelpButtonHint) self.setMinimumSize(800, 600) self.setModal(True) main_layout = QVBoxLayout(self) self.canvas = _DynamicCanvas(vpoints, vlinks, path, slider_path, self) self.canvas.set_monochrome_mode(monochrome) self.canvas.update_pos.connect(self.__set_pos) layout = QHBoxLayout(self) pt_option = QComboBox(self) pt_option.addItems([f"P{p}" for p in range(len(vpoints))]) layout.addWidget(pt_option) value_label = QLabel(self) @Slot(int) def show_values(ind: int): vel, vel_deg = self.canvas.get_vel(ind) acc, acc_deg = self.canvas.get_acc(ind) value_label.setText( f"Velocity: {vel:.04f} ({vel_deg:.04f}deg) | " f"Acceleration: {acc:.04f} ({acc_deg:.04f}deg)") pt_option.currentIndexChanged.connect(show_values) layout.addWidget(value_label) self.pos_label = QLabel(self) layout.addItem( QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) layout.addWidget(self.pos_label) main_layout.addLayout(layout) main_layout.addWidget(self.canvas) layout = QHBoxLayout(self) self.play = QPushButton(QIcon(QPixmap(":/icons/play.png")), "", self) self.play.setCheckable(True) self.play.clicked.connect(self.__play) layout.addWidget(self.play) self.slider = QSlider(Qt.Horizontal, self) self.slider.setMaximum(max(len(p) for p in path) - 1) self.slider.valueChanged.connect(self.canvas.set_index) layout.addWidget(self.slider) layout.addWidget(QLabel("Total times:", self)) factor = QDoubleSpinBox(self) factor.valueChanged.connect(self.canvas.set_factor) factor.setSuffix('s') factor.setRange(0.01, 999999) factor.setValue(10) layout.addWidget(factor) main_layout.addLayout(layout) self.timer = QTimer() self.timer.setInterval(10) self.timer.timeout.connect(self.__move_ind) @Slot() def __move_ind(self): """Move indicator.""" value = self.slider.value() + 1 self.slider.setValue(value) if value > self.slider.maximum(): self.slider.setValue(0) @Slot(float, float) def __set_pos(self, x: float, y: float) -> None: """Set mouse position.""" self.pos_label.setText(f"({x:.04f}, {y:.04f})") @Slot() def __play(self): """Start playing.""" if self.play.isChecked(): self.timer.start() else: self.timer.stop()
class NotificationsWidget(QWidget, VCPWidget): notificationsCleared = Signal(object) def __init__(self, parent=None): super(NotificationsWidget, self).__init__(parent) self.notification_channel = getPlugin("notifications") self.main_layout = QVBoxLayout() self.button_layout = QHBoxLayout() self.all_button = QPushButton() self.info_button = QPushButton() self.warn_button = QPushButton() self.error_button = QPushButton() self.debug_button = QPushButton() self.clear_button = QPushButton() self.all_button.setText("all") self.info_button.setText("info") self.warn_button.setText("warn") self.error_button.setText("error") self.debug_button.setText("debug") self.clear_button.setText("clear") self.all_button.setCheckable(True) self.info_button.setCheckable(True) self.warn_button.setCheckable(True) self.error_button.setCheckable(True) self.debug_button.setCheckable(True) self.all_button.setChecked(True) self.info_button.setChecked(False) self.warn_button.setChecked(False) self.error_button.setChecked(False) self.debug_button.setChecked(False) self.clear_button.clicked.connect(self.clear_all_notifications) self.button_layout.addWidget(self.all_button) self.button_layout.addWidget(self.info_button) self.button_layout.addWidget(self.warn_button) self.button_layout.addWidget(self.error_button) self.button_layout.addWidget(self.debug_button) self.button_layout.addWidget(self.clear_button) self.notification_name = QLabel() self.notification_name.setAlignment(Qt.AlignCenter) self.notification_name.setText("All Notifications") self.all_notification_view = QListView() self.all_notification_model = QStandardItemModel( self.all_notification_view) self.all_notification_model_proxy = QSortFilterProxyModel( self.all_notification_view) self.all_notification_model_proxy.setSourceModel( self.all_notification_model) # self.all_notification_view.setModel(self.all_notification_model) self.all_notification_view.setModel(self.all_notification_model_proxy) self.all_notifications = list() self.main_layout.addWidget(self.notification_name) self.main_layout.addWidget(self.all_notification_view) self.main_layout.addLayout(self.button_layout) self.setLayout(self.main_layout) self.notification_channel.info_message.notify(self.on_info_message) self.notification_channel.warn_message.notify(self.on_warn_message) self.notification_channel.error_message.notify(self.on_error_message) self.notification_channel.debug_message.notify(self.on_debug_message) self.all_button.clicked.connect(self.show_all_notifications) self.info_button.clicked.connect(self.show_info_notifications) self.warn_button.clicked.connect(self.show_warn_notifications) self.error_button.clicked.connect(self.show_error_notifications) self.debug_button.clicked.connect(self.show_debug_notifications) @staticmethod def _get_time(): timestamp = time() dt_object = datetime.fromtimestamp(timestamp) return dt_object.strftime("%d / %b / %Y - %H:%M:%S") @staticmethod def _strip_new_line(message): return message.replace('\n', ' ').replace('\r', '').replace( ' ', ' ').replace(' ', ' ').replace(' ', ' ') def on_info_message(self, message): msg = 'INFO - occurred at: [ {} ]\n{}'.format( NotificationsWidget._get_time(), NotificationsWidget._strip_new_line(message)) notification_item = QStandardItem() notification_item.setText(msg) notification_item.setIcon(QIcon.fromTheme('dialog-information')) notification_item.setEditable(False) self.all_notification_model.appendRow(notification_item) def on_warn_message(self, message): msg = 'WARNING - occurred at: [ {} ]\n{}'.format( NotificationsWidget._get_time(), NotificationsWidget._strip_new_line(message)) notification_item = QStandardItem() notification_item.setText(msg) notification_item.setIcon(QIcon.fromTheme('dialog-warning')) notification_item.setEditable(False) self.all_notification_model.appendRow(notification_item) def on_error_message(self, message): msg = 'ERROR - occurred at: [ {} ]\n{}'.format( NotificationsWidget._get_time(), NotificationsWidget._strip_new_line(message)) LOG.debug('-----on_error_message called: {}'.format(message)) notification_item = QStandardItem() notification_item.setText(msg) notification_item.setIcon(QIcon.fromTheme('dialog-error')) notification_item.setEditable(False) self.all_notification_model.appendRow(notification_item) def on_debug_message(self, message): msg = 'DEBUG - occurred at: [ {} ]\n{}'.format( NotificationsWidget._get_time(), NotificationsWidget._strip_new_line(message)) notification_item = QStandardItem() notification_item.setText(msg) notification_item.setIcon(QIcon.fromTheme('dialog-question')) notification_item.setEditable(False) self.all_notification_model.appendRow(notification_item) def show_all_notifications(self): self.all_button.setChecked(True) self.info_button.setChecked(False) self.warn_button.setChecked(False) self.error_button.setChecked(False) self.debug_button.setChecked(False) self.notification_name.setText("All Notifications") self.all_notification_model_proxy.setFilterRegExp(None) def show_info_notifications(self): self.all_button.setChecked(False) self.info_button.setChecked(True) self.warn_button.setChecked(False) self.error_button.setChecked(False) self.debug_button.setChecked(False) self.notification_name.setText("Information Notifications") self.all_notification_model_proxy.setFilterRegExp( QRegExp("INFO", Qt.CaseSensitive, QRegExp.FixedString)) def show_warn_notifications(self): self.all_button.setChecked(False) self.info_button.setChecked(False) self.warn_button.setChecked(True) self.error_button.setChecked(False) self.debug_button.setChecked(False) self.notification_name.setText("Warning Notifications") self.all_notification_model_proxy.setFilterRegExp( QRegExp("WANRNING", Qt.CaseSensitive, QRegExp.FixedString)) def show_error_notifications(self): self.all_button.setChecked(False) self.info_button.setChecked(False) self.warn_button.setChecked(False) self.error_button.setChecked(True) self.debug_button.setChecked(False) self.notification_name.setText("Error Notifications") self.all_notification_model_proxy.setFilterRegExp( QRegExp("ERROR", Qt.CaseInsensitive, QRegExp.FixedString)) def show_debug_notifications(self): self.all_button.setChecked(False) self.info_button.setChecked(False) self.warn_button.setChecked(False) self.error_button.setChecked(False) self.debug_button.setChecked(True) self.notification_name.setText("Debug Notifications") self.all_notification_model_proxy.setFilterRegExp( QRegExp("DEBUG", Qt.CaseSensitive, QRegExp.FixedString)) def clear_all_notifications(self): self.all_notification_model.clear() self.notificationsCleared.emit(self)
class QtReQueueControls(QWidget): def __init__(self, model, parent=None): super().__init__(parent) self.model = model self._lb_queue_state = QLabel("STOPPED") self._pb_queue_start = QPushButton("Start") self._pb_queue_start.setEnabled(False) self._pb_queue_start.clicked.connect(self._pb_queue_start_clicked) self._pb_queue_stop = QPushButton("Stop") self._pb_queue_stop.setEnabled(False) self._pb_queue_stop.setCheckable(True) self._pb_queue_stop.clicked.connect(self._pb_queue_stop_clicked) self._group_box = QGroupBox("Queue") vbox = QVBoxLayout() vbox.addWidget(self._lb_queue_state, alignment=Qt.AlignHCenter) vbox.addWidget(self._pb_queue_start) vbox.addWidget(self._pb_queue_stop) self._group_box.setLayout(vbox) vbox = QVBoxLayout() vbox.addWidget(self._group_box) self.setLayout(vbox) self.model.events.status_changed.connect(self.on_update_widgets) def on_update_widgets(self, event): # None should be converted to False: is_connected = bool(event.is_connected) status = event.status worker_exists = status.get("worker_environment_exists", False) running_item_uid = status.get("running_item_uid", None) queue_stop_pending = status.get("queue_stop_pending", False) s = "RUNNING" if running_item_uid else "STOPPED" self._lb_queue_state.setText(s) self._pb_queue_start.setEnabled(is_connected and worker_exists and not bool(running_item_uid)) self._pb_queue_stop.setEnabled(is_connected and worker_exists and bool(running_item_uid)) self._pb_queue_stop.setChecked(queue_stop_pending) def _pb_queue_start_clicked(self): try: self.model.queue_start() except Exception as ex: print(f"Exception: {ex}") def _pb_queue_stop_clicked(self): try: if self._pb_queue_stop.isChecked(): print("Stopping the queue") self.model.queue_stop() else: print("Cancelling stop") self.model.queue_stop_cancel() except Exception as ex: print(f"Exception: {ex}")
class QtPluginDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.installer = Installer() self.setup_ui() self.installer.set_output_widget(self.stdout_text) self.installer.process.started.connect(self._on_installer_start) self.installer.process.finished.connect(self._on_installer_done) self.refresh() def _on_installer_start(self): self.show_status_btn.setChecked(True) self.working_indicator.show() self.process_error_indicator.hide() def _on_installer_done(self, exit_code, exit_status): self.working_indicator.hide() if exit_code: self.process_error_indicator.show() else: self.show_status_btn.setChecked(False) self.refresh() self.plugin_sorter.refresh() def refresh(self): self.installed_list.clear() self.available_list.clear() # fetch installed from ...plugins import plugin_manager plugin_manager.discover() # since they might not be loaded yet already_installed = set() for plugin_name, mod_name, distname in plugin_manager.iter_available(): # not showing these in the plugin dialog if plugin_name in ('napari_plugin_engine', ): continue if distname: already_installed.add(distname) meta = standard_metadata(distname) else: meta = {} self.installed_list.addItem( ProjectInfo( normalized_name(distname or ''), meta.get('version', ''), meta.get('url', ''), meta.get('summary', ''), meta.get('author', ''), meta.get('license', ''), ), plugin_name=plugin_name, enabled=plugin_name in plugin_manager.plugins, ) # self.v_splitter.setSizes([70 * self.installed_list.count(), 10, 10]) # fetch available plugins self.worker = create_worker(iter_napari_plugin_info) def _handle_yield(project_info): if project_info.name in already_installed: self.installed_list.tag_outdated(project_info) else: self.available_list.addItem(project_info) self.worker.yielded.connect(_handle_yield) self.worker.finished.connect(self.working_indicator.hide) self.worker.start() def setup_ui(self): self.resize(1080, 640) vlay_1 = QVBoxLayout(self) self.h_splitter = QSplitter(self) vlay_1.addWidget(self.h_splitter) self.h_splitter.setOrientation(Qt.Horizontal) self.v_splitter = QSplitter(self.h_splitter) self.v_splitter.setOrientation(Qt.Vertical) self.v_splitter.setMinimumWidth(500) self.plugin_sorter = QtPluginSorter(parent=self.h_splitter) self.plugin_sorter.layout().setContentsMargins(2, 0, 0, 0) self.plugin_sorter.hide() installed = QWidget(self.v_splitter) lay = QVBoxLayout(installed) lay.setContentsMargins(0, 2, 0, 2) lay.addWidget(QLabel("Installed Plugins")) self.installed_list = QPluginList(installed, self.installer) lay.addWidget(self.installed_list) uninstalled = QWidget(self.v_splitter) lay = QVBoxLayout(uninstalled) lay.setContentsMargins(0, 2, 0, 2) lay.addWidget(QLabel("Available Plugin Packages")) self.available_list = QPluginList(uninstalled, self.installer) lay.addWidget(self.available_list) self.stdout_text = QTextEdit(self.v_splitter) self.stdout_text.setReadOnly(True) self.stdout_text.setObjectName("pip_install_status") self.stdout_text.hide() buttonBox = QHBoxLayout() self.working_indicator = QLabel("loading ...", self) self.process_error_indicator = QLabel(self) self.process_error_indicator.setObjectName("error_label") self.process_error_indicator.hide() load_gif = str(Path(napari.resources.__file__).parent / "loading.gif") mov = QMovie(load_gif) mov.setScaledSize(QSize(18, 18)) self.working_indicator.setMovie(mov) mov.start() self.show_status_btn = QPushButton("Show Status", self) self.show_status_btn.setFixedWidth(100) self.show_sorter_btn = QPushButton("<< Show Sorter", self) self.close_btn = QPushButton("Close", self) self.close_btn.clicked.connect(self.reject) buttonBox.addWidget(self.show_status_btn) buttonBox.addWidget(self.working_indicator) buttonBox.addWidget(self.process_error_indicator) buttonBox.addStretch() buttonBox.addWidget(self.show_sorter_btn) buttonBox.addWidget(self.close_btn) buttonBox.setContentsMargins(0, 0, 4, 0) vlay_1.addLayout(buttonBox) self.show_status_btn.setCheckable(True) self.show_status_btn.setChecked(False) self.show_status_btn.toggled.connect(self._toggle_status) self.show_sorter_btn.setCheckable(True) self.show_sorter_btn.setChecked(False) self.show_sorter_btn.toggled.connect(self._toggle_sorter) self.v_splitter.setStretchFactor(1, 2) self.h_splitter.setStretchFactor(0, 2) def _toggle_sorter(self, show): if show: self.show_sorter_btn.setText(">> Hide Sorter") self.plugin_sorter.show() else: self.show_sorter_btn.setText("<< Show Sorter") self.plugin_sorter.hide() def _toggle_status(self, show): if show: self.show_status_btn.setText("Hide Status") self.stdout_text.show() else: self.show_status_btn.setText("Show Status") self.stdout_text.hide()
class PlotPreferences(QWidget): def __init__(self, mainWindow): super().__init__() self.mainWindow = mainWindow plotStyleGroup = GroupWidget("Plot style") styles = self.mainWindow.plot.getValidStyles() self.customStyle = StyleDesigner( styleKeys=self.mainWindow.plot.getStyleKeys(), symbolKeys=self.mainWindow.plot.getStyleSymbolKeys(), invalidNames=styles) self.customStyle.setEnabled(False) self.customStyle.saveStyle.connect(self._saveStyle) self.plotStyleList = QComboBox() styles = [s.capitalize() for s in styles] styles += ["Add custom theme..."] self.plotStyleList.addItems(styles) self.plotStyleList.currentTextChanged.connect( self._updateCustomStyleWidget) foregroundColour = self.palette().windowText().color() icon = makeForegroundIcon("edit", foregroundColour) self.editPlotStyleButton = QPushButton(icon, "") self.editPlotStyleButton.setCheckable(True) self.editPlotStyleButton.setToolTip("Edit theme") self.editPlotStyleButton.toggled.connect(self._editStyle) icon = makeForegroundIcon("trash", foregroundColour) self.deletePlotStyleButton = QPushButton(icon, "") self.deletePlotStyleButton.setToolTip("Delete theme") self.deletePlotStyleButton.clicked.connect(self._deleteTheme) self.plotStyleList.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.editPlotStyleButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.deletePlotStyleButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) plotStyleBox = QHBoxLayout() plotStyleBox.addWidget(self.plotStyleList) plotStyleBox.addWidget(self.editPlotStyleButton) plotStyleBox.addWidget(self.deletePlotStyleButton) plotStyleGroup.addLayout(plotStyleBox) plotStyleGroup.addWidget(self.customStyle) plotConfigGroup = GroupWidget("Default plot range", layout="vbox") self.plotRangeCombo = QComboBox() ranges = [ "1 month", "3 months", "6 months", "1 year", "Current year", "All" ] self.plotRangeCombo.addItems(ranges) self.customRangeCheckBox = QCheckBox("Custom range") self.customRangeSpinBox = QSpinBox() self.customRangeSpinBox.setSuffix(" months") maxMonths = len(mainWindow.data.splitMonths()) self.customRangeSpinBox.setRange(1, maxMonths) self.customRangeCheckBox.clicked.connect(self.setCustomRange) plotRangeLayout = QHBoxLayout() plotRangeLayout.addWidget(self.plotRangeCombo) customRangeLayout = QHBoxLayout() customRangeLayout.addWidget(self.customRangeCheckBox) customRangeLayout.addWidget(self.customRangeSpinBox) plotConfigGroup.addLayout(plotRangeLayout) plotConfigGroup.addLayout(customRangeLayout) mainLayout = QVBoxLayout() mainLayout.addWidget(plotStyleGroup) mainLayout.addWidget(plotConfigGroup) mainLayout.addStretch(1) self.setLayout(mainLayout) self.setCurrentValues() # apply initial state self.apply() def setCurrentValues(self): self.settings = Settings() self.settings.beginGroup("plot") plotStyle = self.settings.value("style", "dark") self.plotStyleList.setCurrentText(plotStyle.capitalize()) # does setCurrentText not emit currentTextChanged signal? self._enableDisableDeleteButton(plotStyle) self.customStyle.setName(plotStyle) self.customStyle.setStyle(self.mainWindow.plot.getStyle(plotStyle)) customRange = self.settings.value("customRange", False) rng = self.settings.value("range", "All") self.setCustomRange(customRange) if customRange: rng = int(rng) self.customRangeSpinBox.setValue(rng) else: items = [ self.plotRangeCombo.itemText(idx) for idx in range(self.plotRangeCombo.count()) ] idx = items.index(rng) self.plotRangeCombo.setCurrentIndex(idx) self.settings.endGroup() def _saveStyle(self, name, style, setStyle=False): self.mainWindow.plot.addCustomStyle(name, style, setStyle=setStyle) idx = self.plotStyleList.count() - 1 self.plotStyleList.insertItem(idx, name.capitalize()) self.plotStyleList.setCurrentIndex(idx) def _editStyle(self, edit): self.customStyle.setEditMode(edit) self._updateCustomStyleWidget() def apply(self): styleName = self.plotStyleList.currentText().lower() if styleName == "add custom theme...": styleName, styleDct = self.customStyle.getStyle() self._saveStyle(styleName, styleDct, setStyle=True) else: self.mainWindow.plot.setStyle(styleName) customRange = self.customRangeCheckBox.isChecked() if customRange: months = self.customRangeSpinBox.value() else: text = self.plotRangeCombo.currentText() if text == "1 year": text = "12 months" elif text == "Current year": text = f"{date.today().month} months" months = None if text == 'All' else int(text.strip(' months')) self.mainWindow.plot.setXAxisRange(months) self.settings.beginGroup("plot") self.settings.setValue("style", styleName) self.settings.setValue("customRange", customRange) if customRange: self.settings.setValue("range", self.customRangeSpinBox.value()) else: self.settings.setValue("range", self.plotRangeCombo.currentText()) self.settings.endGroup() @Slot(bool) def setCustomRange(self, custom): self.customRangeCheckBox.setChecked(custom) if custom: self.customRangeSpinBox.setEnabled(True) self.plotRangeCombo.setEnabled(False) else: self.customRangeSpinBox.setEnabled(False) self.plotRangeCombo.setEnabled(True) def _updateCustomStyleWidget(self, name=None): if name is None or name == "Add custom theme...": self.customStyle.setEnabled(True) if name is None: name = self.customStyle.name else: name = f"custom-{self.customStyle.name}" self.customStyle.setName(name) else: name = name.lower() style = self.mainWindow.plot.getStyle(name) self.customStyle.setStyle(style, name=name) self.customStyle.setEnabled(False) self._enableDisableDeleteButton(name) def _enableDisableDeleteButton(self, plotStyle): if plotStyle in self.mainWindow.plot.getDefaultStyles(): self.deletePlotStyleButton.setEnabled(False) self.deletePlotStyleButton.setToolTip( "Cannot delete default theme") else: self.deletePlotStyleButton.setEnabled(True) self.deletePlotStyleButton.setToolTip("Delete theme") def _deleteTheme(self): styleName = self.plotStyleList.currentText() items = [ self.plotStyleList.itemText(idx) for idx in range(self.plotStyleList.count()) ] idx = items.index(styleName) self.plotStyleList.removeItem(idx) self.mainWindow.plot.removeCustomStyle(styleName.lower())
class TogglePanel(QWidget): """ Generic Panel Widget Displays a widget below QPushButton that hides and shows the contents. It is up to subclasses to re-point the attribute :attr:`.contents` to the widget whose visibility you would like to toggle. By default, it is assumed that the Panel is initialized with the :attr:`.contents` widget as visible, however the contents will be hidden and the button synced to the proper position if :meth:`.show_contents` is called after instance creation Parameters ---------- title : str Title of Panel. This will be the text on the QPushButton parent : QWidget Attributes ---------- contents : QWidget Widget whose visibility is controlled via the QPushButton """ def __init__(self, title, parent=None): super().__init__(parent=parent) # Create Widget Infrastructure self.title = title self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(2, 2, 2, 2) self.layout().setSpacing(5) # Create button control # Assuming widget is visible, set the button as checked self.contents = None self.hide_button = QPushButton(self.title) self.hide_button.setCheckable(True) self.hide_button.setChecked(True) self.layout().addWidget(self.hide_button) self.hide_button.clicked.connect(self.show_contents) @Slot(bool) def show_contents(self, show): """ Show the contents of the Widget Hides the :attr:`.contents` QWidget and sets the :attr:`.hide_button` to the proper status to indicate whether the widget is hidden or not Parameters ---------- show : bool """ # Configure our button in case this slot was called elsewhere self.hide_button.setChecked(show) # Show or hide the widget if the contents exist if self.contents: if show: self.show() self.contents.show() else: self.contents.hide()
class Dimension(QWidget): stateChanged = Signal(int) valueChanged = Signal() """ pass in dimension state: one of (State.X, State.Y, State.NONE, State.DISABLE) Can be run independently by: from mantidqt.widgets.sliceviewer.dimensionwidget import Dimension from qtpy.QtWidgets import QApplication app = QApplication([]) window = Dimension({'minimum':-1.1, 'number_of_bins':11, 'width':0.2, 'name':'Dim0', 'units':'A'}) window.show() app.exec_() """ def __init__(self, dim_info, number=0, state=State.NONE, parent=None): super().__init__(parent) self.minimum = dim_info['minimum'] self.nbins = dim_info['number_of_bins'] self.width = dim_info['width'] self.number = number self.layout = QHBoxLayout(self) self.name = QLabel(dim_info['name']) self.units = QLabel(dim_info['units']) self.x = QPushButton('X') self.x.setFixedSize(32, 32) self.x.setCheckable(True) self.x.clicked.connect(self.x_clicked) self.y = QPushButton('Y') self.y.setFixedSize(32, 32) self.y.setCheckable(True) self.y.clicked.connect(self.y_clicked) self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.nbins - 1) self.slider.valueChanged.connect(self.slider_changed) self.spinbox = QDoubleSpinBox() self.spinbox.setDecimals(3) self.spinbox.setRange(self.get_bin_center(0), self.get_bin_center(self.nbins - 1)) self.spinbox.setSingleStep(self.width) self.spinbox.editingFinished.connect(self.spinbox_changed) self.layout.addWidget(self.name) self.layout.addWidget(self.x) self.layout.addWidget(self.y) self.layout.addWidget(self.slider, stretch=1) self.layout.addStretch(0) self.layout.addWidget(self.spinbox) self.layout.addWidget(self.units) self.set_value(0) if self.nbins < 2: state = State.DISABLE self.set_state(state) def set_state(self, state): self.state = state if self.state == State.X: self.x.setChecked(True) self.y.setChecked(False) self.slider.hide() self.spinbox.hide() self.units.hide() elif self.state == State.Y: self.x.setChecked(False) self.y.setChecked(True) self.slider.hide() self.spinbox.hide() self.units.hide() elif self.state == State.NONE: self.x.setChecked(False) self.y.setChecked(False) self.slider.show() self.spinbox.show() self.units.show() else: self.x.setChecked(False) self.x.setDisabled(True) self.y.setChecked(False) self.y.setDisabled(True) self.slider.hide() self.spinbox.show() self.spinbox.setDisabled(True) self.units.show() def get_state(self): return self.state def x_clicked(self): old_state = self.state self.set_state(State.X) if self.state != old_state: self.stateChanged.emit(self.number) def y_clicked(self): old_state = self.state self.set_state(State.Y) if self.state != old_state: self.stateChanged.emit(self.number) def spinbox_changed(self): self.value = self.spinbox.value() self.update_slider() def slider_changed(self): self.value = self.get_bin_center(self.slider.value()) self.update_spinbox() self.valueChanged.emit() def get_bin_center(self, n): return (n + 0.5) * self.width + self.minimum def update_slider(self): i = (self.value - self.minimum) / self.width self.slider.setValue(int(min(max(i, 0), self.nbins - 1))) def update_spinbox(self): self.spinbox.setValue(self.value) def set_value(self, value): self.value = value self.update_slider() self.update_spinbox() def get_value(self): return self.value
class RunDialog(QDialog): simulation_done = Signal(bool, str) simulation_termination_request = Signal() def __init__(self, config_file, run_model, simulation_arguments, parent=None): QDialog.__init__(self, parent) self.setWindowFlags(Qt.Window) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.setModal(True) self.setWindowModality(Qt.WindowModal) self.setWindowTitle("Simulations - {}".format(config_file)) self._snapshot_model = SnapshotModel(self) self._run_model = run_model self._isDetailedDialog = False self._minimum_width = 1200 ert = None if isinstance(run_model, BaseRunModel): ert = run_model.ert() self._simulations_argments = simulation_arguments self._ticker = QTimer(self) self._ticker.timeout.connect(self._on_ticker) progress_proxy_model = ProgressProxyModel(self._snapshot_model, parent=self) self._total_progress_label = QLabel(_TOTAL_PROGRESS_TEMPLATE.format(0), self) self._total_progress_bar = QProgressBar(self) self._total_progress_bar.setRange(0, 100) self._total_progress_bar.setTextVisible(False) self._iteration_progress_label = QLabel(self) self._progress_view = ProgressView(self) self._progress_view.setModel(progress_proxy_model) self._progress_view.setIndeterminate(True) legend_view = LegendView(self) legend_view.setModel(progress_proxy_model) self._tab_widget = QTabWidget(self) self._snapshot_model.rowsInserted.connect(self.on_new_iteration) self._job_label = QLabel(self) self._job_model = JobListProxyModel(self, 0, 0, 0, 0) self._job_model.setSourceModel(self._snapshot_model) self._job_view = QTableView(self) self._job_view.clicked.connect(self._job_clicked) self._open_files = {} self._job_view.setModel(self._job_model) self.running_time = QLabel("") self.plot_tool = PlotTool(config_file) self.plot_tool.setParent(self) self.plot_button = QPushButton(self.plot_tool.getName()) self.plot_button.clicked.connect(self.plot_tool.trigger) self.plot_button.setEnabled(ert is not None) self.kill_button = QPushButton("Kill Simulations") self.done_button = QPushButton("Done") self.done_button.setHidden(True) self.restart_button = QPushButton("Restart") self.restart_button.setHidden(True) self.show_details_button = QPushButton("Show Details") self.show_details_button.setCheckable(True) size = 20 spin_movie = resourceMovie("ide/loading.gif") spin_movie.setSpeed(60) spin_movie.setScaledSize(QSize(size, size)) spin_movie.start() self.processing_animation = QLabel() self.processing_animation.setMaximumSize(QSize(size, size)) self.processing_animation.setMinimumSize(QSize(size, size)) self.processing_animation.setMovie(spin_movie) button_layout = QHBoxLayout() button_layout.addWidget(self.processing_animation) button_layout.addWidget(self.running_time) button_layout.addStretch() button_layout.addWidget(self.show_details_button) button_layout.addWidget(self.plot_button) button_layout.addWidget(self.kill_button) button_layout.addWidget(self.done_button) button_layout.addWidget(self.restart_button) button_widget_container = QWidget() button_widget_container.setLayout(button_layout) layout = QVBoxLayout() layout.addWidget(self._total_progress_label) layout.addWidget(self._total_progress_bar) layout.addWidget(self._iteration_progress_label) layout.addWidget(self._progress_view) layout.addWidget(legend_view) layout.addWidget(self._tab_widget) layout.addWidget(self._job_label) layout.addWidget(self._job_view) layout.addWidget(button_widget_container) self.setLayout(layout) self.kill_button.clicked.connect(self.killJobs) self.done_button.clicked.connect(self.accept) self.restart_button.clicked.connect(self.restart_failed_realizations) self.show_details_button.clicked.connect(self.toggle_detailed_progress) self.simulation_done.connect(self._on_simulation_done) self.setMinimumWidth(self._minimum_width) self._setSimpleDialog() def _setSimpleDialog(self) -> None: self._isDetailedDialog = False self._tab_widget.setVisible(False) self._job_label.setVisible(False) self._job_view.setVisible(False) self.show_details_button.setText("Show Details") def _setDetailedDialog(self) -> None: self._isDetailedDialog = True self._tab_widget.setVisible(True) self._job_label.setVisible(True) self._job_view.setVisible(True) self.show_details_button.setText("Hide Details") @Slot(QModelIndex, int, int) def on_new_iteration(self, parent: QModelIndex, start: int, end: int) -> None: if not parent.isValid(): iter = start self._iteration_progress_label.setText( f"Progress for iteration {iter}") widget = RealizationWidget(iter) widget.setSnapshotModel(self._snapshot_model) widget.currentChanged.connect(self._select_real) self._tab_widget.addTab(widget, f"Realizations for iteration {iter}") @Slot(QModelIndex) def _job_clicked(self, index): if not index.isValid(): return selected_file = index.data(FileRole) if selected_file and selected_file not in self._open_files: job_name = index.siblingAtColumn(0).data() viewer = FileDialog( selected_file, job_name, index.row(), index.model().get_real(), index.model().get_iter(), self, ) self._open_files[selected_file] = viewer viewer.finished.connect( lambda _, f=selected_file: self._open_files.pop(f)) elif selected_file in self._open_files: self._open_files[selected_file].raise_() @Slot(QModelIndex) def _select_real(self, index): step = 0 stage = 0 real = index.row() iter_ = index.model().get_iter() self._job_model.set_step(iter_, real, stage, step) self._job_label.setText( f"Realization id {index.data(RealIens)} in iteration {iter_}") self._job_view.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) # Clear the selection in the other tabs for i in range(0, self._tab_widget.count()): if i != self._tab_widget.currentIndex(): self._tab_widget.widget(i).clearSelection() def reject(self): return def closeEvent(self, QCloseEvent): if self._run_model.isFinished(): self.simulation_done.emit(self._run_model.hasRunFailed(), self._run_model.getFailMessage()) else: # Kill jobs if dialog is closed if self.killJobs() != QMessageBox.Yes: QCloseEvent.ignore() def startSimulation(self): self._run_model.reset() self._snapshot_model.reset() self._tab_widget.clear() def run(): asyncio.set_event_loop(asyncio.new_event_loop()) self._run_model.startSimulations(self._simulations_argments) simulation_thread = Thread(name="ert_gui_simulation_thread") simulation_thread.setDaemon(True) simulation_thread.run = run simulation_thread.start() self._ticker.start(1000) tracker = create_tracker( self._run_model, num_realizations=self._simulations_argments["active_realizations"]. count(), ee_config=self._simulations_argments.get("ee_config", None), ) worker = TrackerWorker(tracker) worker_thread = QThread() worker.done.connect(worker_thread.quit) worker.consumed_event.connect(self._on_tracker_event) worker.moveToThread(worker_thread) self.simulation_done.connect(worker.stop) self._worker = worker self._worker_thread = worker_thread worker_thread.started.connect(worker.consume_and_emit) self._worker_thread.start() def killJobs(self): msg = "Are you sure you want to kill the currently running simulations?" if self._run_model.getQueueStatus().get( JobStatusType.JOB_QUEUE_UNKNOWN, 0) > 0: msg += "\n\nKilling a simulation with unknown status will not kill the realizations already submitted!" kill_job = QMessageBox.question(self, "Kill simulations?", msg, QMessageBox.Yes | QMessageBox.No) if kill_job == QMessageBox.Yes: # Normally this slot would be invoked by the signal/slot system, # but the worker is busy tracking the evaluation. self._worker.request_termination() self.reject() return kill_job @Slot(bool, str) def _on_simulation_done(self, failed, failed_msg): self.processing_animation.hide() self.kill_button.setHidden(True) self.done_button.setHidden(False) self.restart_button.setVisible(self.has_failed_realizations()) self.restart_button.setEnabled(self._run_model.support_restart) self._total_progress_bar.setValue(100) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format(100)) if failed: QMessageBox.critical( self, "Simulations failed!", f"The simulation failed with the following error:\n\n{failed_msg}", ) @Slot() def _on_ticker(self): runtime = self._run_model.get_runtime() self.running_time.setText(format_running_time(runtime)) @Slot(object) def _on_tracker_event(self, event): if isinstance(event, EndEvent): self.simulation_done.emit(event.failed, event.failed_msg) self._worker.stop() self._ticker.stop() elif isinstance(event, FullSnapshotEvent): if event.snapshot is not None: self._snapshot_model._add_snapshot(event.snapshot, event.iteration) self._progress_view.setIndeterminate(event.indeterminate) progress = int(event.progress * 100) self._total_progress_bar.setValue(progress) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format(progress)) elif isinstance(event, SnapshotUpdateEvent): if event.partial_snapshot is not None: self._snapshot_model._add_partial_snapshot( event.partial_snapshot, event.iteration) self._progress_view.setIndeterminate(event.indeterminate) progress = int(event.progress * 100) self._total_progress_bar.setValue(progress) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format(progress)) def has_failed_realizations(self): completed = self._run_model.completed_realizations_mask initial = self._run_model.initial_realizations_mask for (index, successful) in enumerate(completed): if initial[index] and not successful: return True return False def count_successful_realizations(self): """ Counts the realizations completed in the prevoius ensemble run :return: """ completed = self._run_model.completed_realizations_mask return completed.count(True) def create_mask_from_failed_realizations(self): """ Creates a BoolVector mask representing the failed realizations :return: Type BoolVector """ completed = self._run_model.completed_realizations_mask initial = self._run_model.initial_realizations_mask inverted_mask = BoolVector(default_value=False) for (index, successful) in enumerate(completed): inverted_mask[index] = initial[index] and not successful return inverted_mask def restart_failed_realizations(self): msg = QMessageBox(self) msg.setIcon(QMessageBox.Information) msg.setText( "Note that workflows will only be executed on the restarted realizations and that this might have unexpected consequences." ) msg.setWindowTitle("Restart Failed Realizations") msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) result = msg.exec_() if result == QMessageBox.Ok: self.restart_button.setVisible(False) self.kill_button.setVisible(True) self.done_button.setVisible(False) active_realizations = self.create_mask_from_failed_realizations() self._simulations_argments[ "active_realizations"] = active_realizations self._simulations_argments[ "prev_successful_realizations"] = self._simulations_argments.get( "prev_successful_realizations", 0) self._simulations_argments[ "prev_successful_realizations"] += self.count_successful_realizations( ) self.startSimulation() @Slot() def toggle_detailed_progress(self): if self._isDetailedDialog: self._setSimpleDialog() else: self._setDetailedDialog() self.adjustSize()