class ProgressBar(QWidget): def __init__(self, parent=None) -> None: super().__init__(parent) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.pbar = QProgressBar() self.description_label = QLabel() self.eta_label = QLabel() layout = QHBoxLayout() layout.addWidget(self.description_label) layout.addWidget(self.pbar) layout.addWidget(self.eta_label) self.setLayout(layout) def setRange(self, min, max): self.pbar.setRange(min, max) def _set_value(self, value): self.pbar.setValue(value) QApplication.processEvents() def _get_value(self): return self.pbar.value() def _set_description(self, desc): self.description_label.setText(desc) QApplication.processEvents() def _set_eta(self, eta): self.eta_label.setText(eta)
class QtLabeledProgressBar(QWidget): """QProgressBar with QLabels for description and ETA.""" def __init__(self, parent: Optional[QWidget] = None, prog: progress = None) -> None: super().__init__(parent) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.progress = prog self.qt_progress_bar = QProgressBar() self.description_label = QLabel() self.eta_label = QLabel() base_layout = QVBoxLayout() pbar_layout = QHBoxLayout() pbar_layout.addWidget(self.description_label) pbar_layout.addWidget(self.qt_progress_bar) pbar_layout.addWidget(self.eta_label) base_layout.addLayout(pbar_layout) line = QFrame(self) line.setObjectName("QtCustomTitleBarLine") line.setFixedHeight(1) base_layout.addWidget(line) self.setLayout(base_layout) def setRange(self, min, max): self.qt_progress_bar.setRange(min, max) def setValue(self, value): self.qt_progress_bar.setValue(value) QApplication.processEvents() def setDescription(self, value): if not value.endswith(': '): value = f'{value}: ' self.description_label.setText(value) QApplication.processEvents() def _set_value(self, event): self.setValue(event.value) def _get_value(self): return self.qt_progress_bar.value() def _set_description(self, event): self.setDescription(event.value) def _make_indeterminate(self, event): self.setRange(0, 0) def _set_eta(self, event): self.eta_label.setText(event.value) def _set_total(self, event): self.setRange(0, event.value)
def __init__(self): """Create our windgets. """ super().__init__() layout = QVBoxLayout() # For our "uptime" timer. self.start_time = time.time() # Label for our progress bar. bar_label = QLabel("Draw Time:") layout.addWidget(bar_label) # Progress bar is not used for "progress", it's just a bar graph to show # the "draw time", the duration of the "UpdateRequest" event. bar = QProgressBar() bar.setRange(0, 100) bar.setValue(50) bar.setFormat("%vms") layout.addWidget(bar) self.bar = bar # We let the user set the "slow event" threshold. self.thresh_ms = self.THRESH_DEFAULT self.thresh_combo = QComboBox() self.thresh_combo.addItems(self.THRESH_OPTIONS) self.thresh_combo.activated[str].connect(self._change_thresh) self.thresh_combo.setCurrentText(str(self.thresh_ms)) combo_layout = QHBoxLayout() combo_layout.addWidget(QLabel("Show Events Slower Than:")) combo_layout.addWidget(self.thresh_combo) combo_layout.addWidget(QLabel("milliseconds")) combo_layout.addItem( QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) ) layout.addLayout(combo_layout) # We log slow events to this window. self.log = TextLog() layout.addWidget(self.log) # Uptime label. To indicate if the widget is getting updated. label = QLabel('') layout.addWidget(label) self.timer_label = label self.setLayout(layout) # Update us with a timer. self.timer = QTimer(self) self.timer.timeout.connect(self.update) self.timer.setInterval(self.UPDATE_MS) self.timer.start()
class FileProgressBar(QWidget): """Simple progress bar with a label""" MAX_LABEL_LENGTH = 60 def __init__(self, parent, *args, **kwargs): QWidget.__init__(self, parent) self.pap = parent self.status_text = QLabel(self) self.bar = QProgressBar(self) self.bar.setRange(0, 0) layout = QVBoxLayout() layout.addWidget(self.status_text) layout.addWidget(self.bar) self.setLayout(layout) def __truncate(self, text): ellipsis = '...' part_len = (self.MAX_LABEL_LENGTH - len(ellipsis)) / 2.0 left_text = text[:int(math.ceil(part_len))] right_text = text[-int(math.floor(part_len)):] return left_text + ellipsis + right_text @Slot(str, int) def set_label_file(self, file, size): text = self.__truncate(file) status_str = 'Downloading file list: {0} ({1})'.format( text, humanize.naturalsize(size)) self.status_text.setText(status_str) def set_bounds(self, a, b): self.bar.setRange(a, b) def reset_files(self): self.status_text.setText(" Downloading file(s)...") self.bar.show() def reset_status(self): self.status_text.setText(" Download Complete!") self.bar.hide() @Slot(str, int, int, int) def update_progress(self, file, num_chunks, bytes_recv, total_bytes): text = " Downloading {0} - {1}/{2} (Chunk {3})" self.status_text.setText( text.format(file, humanize.naturalsize(bytes_recv), humanize.naturalsize(total_bytes), num_chunks)) self.bar.setValue(bytes_recv)
class WaitingDialog(QDialog): def __init__(self, thread: QThread, text="", parent=None, exception_hook=None): super().__init__(parent) label = QLabel(text) self.exception_hook = exception_hook self.progress = QProgressBar() self.progress.setRange(0, 0) self.cancel_btn = QPushButton("Cancel") self.cancel_btn.setDisabled(True) self._error_caught = False layout = QHBoxLayout() layout.addWidget(label) layout.addWidget(self.progress) layout.addWidget(self.cancel_btn) self.cancel_btn.clicked.connect(self.close) thread.finished.connect(self.accept_if_no_reject) self.thread_to_wait = thread self.setLayout(layout) self.setResult(QDialog.Accepted) if isinstance(thread, ProgressTread): thread.range_changed.connect(self.progress.setRange) thread.step_changed.connect(self.progress.setValue) thread.error_signal.connect(self.error_catch) def error_catch(self, error): # print(self.thread() == QApplication.instance().thread(), error, isinstance(error, Exception)) self._error_caught = True self.reject() if self.exception_hook: self.exception_hook(error) else: raise error def accept_if_no_reject(self): if self._error_caught: self.reject() else: self.accept() def exec(self): self.exec_() def exec_(self): self.thread_to_wait.start() return super().exec_()
class ProgressBar(QWidget): """QProgressBar with QLabels for description and ETA.""" def __init__(self, parent=None) -> None: super().__init__(parent) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.pbar = QProgressBar() self.description_label = QLabel() self.eta_label = QLabel() base_layout = QVBoxLayout() pbar_layout = QHBoxLayout() pbar_layout.addWidget(self.description_label) pbar_layout.addWidget(self.pbar) pbar_layout.addWidget(self.eta_label) base_layout.addLayout(pbar_layout) line = QFrame(self) line.setObjectName("QtCustomTitleBarLine") line.setFixedHeight(1) base_layout.addWidget(line) self.setLayout(base_layout) def setRange(self, min, max): self.pbar.setRange(min, max) def _set_value(self, value): self.pbar.setValue(value) QApplication.processEvents() def _get_value(self): return self.pbar.value() def _set_description(self, desc): self.description_label.setText(desc) QApplication.processEvents() def _set_eta(self, eta): self.eta_label.setText(eta)
class ProgressView(QWidget): def __init__(self, parent=None) -> None: super(ProgressView, self).__init__(parent) self._progress_tree_view = QTreeView(self) self._progress_tree_view.setHeaderHidden(True) self._progress_tree_view.setItemsExpandable(False) self._progress_tree_view.setItemDelegate(ProgressDelegate(self)) self._progress_tree_view.setRootIsDecorated(False) self._progress_tree_view.setFixedHeight(30) self._progress_tree_view.setVerticalScrollBarPolicy( Qt.ScrollBarAlwaysOff) self._progress_bar = QProgressBar(self) self._progress_bar.setRange(0, 0) self._progress_bar.setFixedHeight(30) self._progress_bar.setVisible(False) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self._progress_tree_view) layout.addWidget(self._progress_bar) self.setLayout(layout) self.setFixedHeight(30) def setModel(self, model) -> None: self._progress_tree_view.setModel(model) @Slot(bool) def setIndeterminate(self, b: bool) -> None: if b: self._progress_bar.setVisible(True) self._progress_tree_view.setVisible(False) else: self._progress_bar.setVisible(False) self._progress_tree_view.setVisible(True)
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()
class KiteInstallation(QWidget): """Kite progress installation widget.""" def __init__(self, parent): super(KiteInstallation, self).__init__(parent) # Left side action_layout = QVBoxLayout() progress_layout = QHBoxLayout() self._progress_widget = QWidget(self) self._progress_widget.setFixedHeight(50) self._progress_filter = HoverEventFilter() self._progress_bar = QProgressBar(self) self._progress_bar.setFixedWidth(180) self._progress_widget.installEventFilter(self._progress_filter) self.cancel_button = QPushButton() self.cancel_button.setIcon(ima.icon('DialogCloseButton')) self.cancel_button.hide() progress_layout.addWidget(self._progress_bar, alignment=Qt.AlignLeft) progress_layout.addWidget(self.cancel_button) self._progress_widget.setLayout(progress_layout) self._progress_label = QLabel(_('Downloading')) install_info = QLabel( _("Kite comes with a native app called the Copilot <br>" "which provides you with real time <br>" "documentation as you code.<br><br>" "When Kite is done installing, the Copilot will <br>" "launch automatically and guide you throught the <br>" "rest of the setup process.")) button_layout = QHBoxLayout() self.ok_button = QPushButton(_('OK')) button_layout.addStretch() button_layout.addWidget(self.ok_button) button_layout.addStretch() action_layout.addStretch() action_layout.addWidget(self._progress_label) action_layout.addWidget(self._progress_widget) action_layout.addWidget(install_info) action_layout.addSpacing(10) action_layout.addLayout(button_layout) action_layout.addStretch() # Right side copilot_image_source = get_image_path('kite_copilot.png') copilot_image = QPixmap(copilot_image_source) copilot_label = QLabel() screen = QApplication.primaryScreen() device_pixel_ratio = screen.devicePixelRatio() if device_pixel_ratio > 1: copilot_image.setDevicePixelRatio(device_pixel_ratio) copilot_label.setPixmap(copilot_image) else: image_height = int(copilot_image.height() * 0.4) image_width = int(copilot_image.width() * 0.4) copilot_label.setPixmap( copilot_image.scaled(image_width, image_height, Qt.KeepAspectRatio, Qt.SmoothTransformation)) # Layout general_layout = QHBoxLayout() general_layout.addLayout(action_layout) general_layout.addWidget(copilot_label) self.setLayout(general_layout) # Signals self._progress_filter.sig_hover_enter.connect( lambda: self.cancel_button.show()) self._progress_filter.sig_hover_leave.connect( lambda: self.cancel_button.hide()) def update_installation_status(self, status): """Update installation status (downloading, installing, finished).""" self._progress_label.setText(status) if status == INSTALLING: self._progress_bar.setRange(0, 0) def update_installation_progress(self, current_value, total): """Update installation progress bar.""" self._progress_bar.setMaximum(total) self._progress_bar.setValue(current_value)
class AbortWindow(QDialog): """ Displays busy message and provides abort button. The class serves SmoothCube, WorkerThread and SelectSmoothing. """ def __init__(self, parent=None): """ init abort or notification ui. Displays while smoothing freezes the application. Allows abort button to be added if needed. """ super(AbortWindow, self).__init__(parent) self.setModal(False) self.setWindowFlags(self.windowFlags() | Qt.Tool) self.parent = parent self.label_a_1 = QLabel("Executing smoothing algorithm.") self.label_a_2 = QLabel("This may take several minutes.") self.abort_button = QPushButton("Abort") self.abort_button.clicked.connect(self.abort) self.pb = QProgressBar(self) self.pb_counter = 0 self.abort_flag = False # vbl is short for Vertical Box Layout vbl = QVBoxLayout() vbl.addWidget(self.label_a_1) vbl.addWidget(self.label_a_2) vbl.addWidget(self.pb) vbl.addWidget(self.abort_button) self.setLayout(vbl) self.show() def init_pb(self, start, end): """ Init the progress bar :param start: Start Value :param end: End Value """ self.pb.setRange(start, end) self.pb_counter = start def update_pb(self): """ This function is called in the worker thread to update the progress bar and checks if the local class variable abort_flag is active. If the abort button is clicked, the main thread will set abort_flag to True. The next time the worker thread calls this function, a custom exception is raised terminating the calculation. The exception is handled by the WorkerThread class. :raises: AbortException: terminating smoothing calculation """ if self.abort_flag: raise AbortException("Abort Calculation") self.pb_counter += 1 self.pb.setValue(self.pb_counter) QApplication.processEvents() def abort(self): """Abort calculation""" self.abort_flag = True self.parent.clean_up() def smoothing_done(self, component_id=None): """Notify user success""" self.hide() if component_id is None: message = "The result has been added as a" \ " new component of the input Data." \ " The new component can be accessed" \ " in the viewer drop-downs." else: message = "The result has been added as" \ " \"{0}\" and can be selected" \ " in the viewer drop-down menu.".format(component_id) info = QMessageBox.information(self, "Success", message) self.clean_up() def print_error(self, exception): """Print error message""" if "signal only works in main thread" in str(exception): message = "Smoothing Failed!\n\n" + "Please update your SpectralCube package" else: message = "Smoothing Failed!\n\n" + str(exception) info = QMessageBox.critical(self, "Error", message) self.clean_up() def clean_up(self): self.parent.clean_up() def keyPressEvent(self, e): if e.key() == Qt.Key_Escape: self.abort()
class MainWindow(QMainWindow): def __init__(self, title): super().__init__() self.setWindowTitle(title) self.lib_path = "" self.final_title = "" analysis_icon = QIcon(os.path.join(icons_dir, "icon.png")) stack_icon = QIcon(os.path.join(icons_dir, "icon_stack.png")) self.analysis_button = QToolButton(self) self.analysis_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.analysis_button.setIcon(analysis_icon) # TODO use more general solution for text wrapping self.analysis_button.setText(ANALYSIS_NAME.replace(" ", "\n")) self.analysis_button.setIconSize(QSize(100, 100)) self.mask_button = QToolButton(self) self.mask_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mask_button.setIcon(stack_icon) self.mask_button.setText(MASK_NAME.replace(" ", "\n")) self.mask_button.setIconSize(QSize(100, 100)) self.analysis_button.clicked.connect(self.launch_analysis) self.mask_button.clicked.connect(self.launch_mask) self.progress = QProgressBar() self.progress.setHidden(True) layout = QGridLayout() layout.addWidget(self.progress, 0, 0, 1, 2) layout.addWidget(self.analysis_button, 1, 1) layout.addWidget(self.mask_button, 1, 0) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) self.setWindowIcon(analysis_icon) self.prepare = None self.wind = None self._update_theme() def _update_theme(self): napari_settings = napari_get_settings(state_store.save_folder) with warnings.catch_warnings(): warnings.simplefilter("ignore", FutureWarning) theme = get_theme(napari_settings.appearance.theme) # TODO understand qss overwrite mechanism self.setStyleSheet(napari_template(get_stylesheet(), **theme)) def _launch_begin(self): self.progress.setVisible(True) self.progress.setRange(0, 0) self.analysis_button.setDisabled(True) self.mask_button.setDisabled(True) import_config() def launch_analysis(self): self._launch_begin() self._launch_analysis() self.prepare.start() def _launch_analysis(self): self.lib_path = "PartSeg._roi_analysis.main_window" self.final_title = f"{APP_NAME} {ANALYSIS_NAME}" self.prepare = Prepare(self.lib_path) self.prepare.finished.connect(self.launch) def launch_mask(self): self._launch_begin() self._launch_mask() self.prepare.start() def _launch_mask(self): self.lib_path = "PartSeg._roi_mask.main_window" self.final_title = f"{APP_NAME} {MASK_NAME}" self.prepare = Prepare(self.lib_path) self.prepare.finished.connect(self.launch) def window_shown(self): self.close() def launch(self): if self.prepare.result is None: self.close() return if self.prepare.errors: errors_message = QMessageBox() errors_message.setText("There are errors during start") errors_message.setInformativeText( "During load saved state some of data could not be load properly\n" "The files has prepared backup copies in state directory (Help > State directory)" ) errors_message.setStandardButtons(QMessageBox.Ok) text = "\n".join("File: " + x[0] + "\n" + str(x[1]) for x in self.prepare.errors) errors_message.setDetailedText(text) errors_message.exec_() wind = self.prepare.result(title=self.final_title, signal_fun=self.window_shown) wind.show() self.wind = wind
class DialogChannels(DialogBase): """Dialog to add delete and select active conda package channels.""" sig_channels_updated = Signal(object, object) # added, removed sig_setup_ready = Signal() sig_check_ready = Signal() WIDTH = 550 def __init__(self, parent=None): """Dialog to add delete and select active conda pacakge channels .""" super(DialogChannels, self).__init__(parent) self._parent = parent self._conda_url = 'https://conda.anaconda.org' self.api = AnacondaAPI() self.initial_sources = None self.config_sources = None self.style_sheet = None self._setup_ready = False self._conda_url_setup_ready = False # Widgets self.list = ListWidgetChannels(parent=self, api=self.api) self.label_info = LabelBase( 'Manage channels you want Navigator to include.') self.label_status = LabelBase('Collecting sources...') self.progress_bar = QProgressBar(self) self.button_add = ButtonNormal('Add...') self.button_cancel = ButtonNormal('Cancel') self.button_ok = ButtonPrimary('Update channels') # Widget setup self.frame_title_bar.setVisible(False) self.list.setFrameStyle(QFrame.NoFrame) self.list.setFrameShape(QFrame.NoFrame) self.setWindowFlags(self.windowFlags() | Qt.Popup) self.setWindowOpacity(0.96) self.setMinimumHeight(300) self.setMinimumWidth(self.WIDTH) self.setModal(True) # Layout layout_button = QHBoxLayout() layout_button.addWidget(self.label_info) layout_button.addStretch() layout_button.addWidget(self.button_add) layout_ok = QHBoxLayout() layout_ok.addWidget(self.label_status) layout_ok.addWidget(SpacerHorizontal()) layout_ok.addWidget(self.progress_bar) layout_ok.addWidget(SpacerHorizontal()) layout_ok.addStretch() layout_ok.addWidget(self.button_cancel) layout_ok.addWidget(SpacerHorizontal()) layout_ok.addWidget(self.button_ok) layout = QVBoxLayout() layout.addLayout(layout_button) layout.addWidget(SpacerVertical()) layout.addWidget(self.list) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_ok) self.setLayout(layout) # Signals self.button_add.clicked.connect(self.add_channel) self.button_ok.clicked.connect(self.update_channels) self.button_cancel.clicked.connect(self.reject) self.list.sig_status_updated.connect(self.update_status) self.list.sig_channel_added.connect( lambda v=None: self.set_tab_order()) self.list.sig_channel_added.connect( lambda v=None: self.button_ok.setFocus()) self.list.sig_channel_removed.connect( lambda v=None: self.set_tab_order()) self.list.sig_channel_removed.connect( lambda v=None: self.button_ok.setFocus()) self.list.sig_channel_checked.connect(self.sig_check_ready) self.list.sig_channel_status.connect(self.refresh) self.button_add.setDisabled(True) self.button_ok.setDisabled(True) self.button_cancel.setDisabled(True) self.update_status(action='Collecting sources...', value=0, max_value=0) @staticmethod def _group_sources_and_channels(sources): """ Flatten sources and channels dictionary to list of tuples. [(source, channel), (source, channel)...] """ grouped = [] for source, channels in sources.items(): for channel in channels: grouped.append((source, channel)) return grouped def keyPressEvent(self, event): """Override Qt method.""" key = event.key() if key in [Qt.Key_Escape]: if self.list.is_editing: self.refresh() self.list.is_editing = False else: self.reject() # --- Public API # ------------------------------------------------------------------------- def update_style_sheet(self, style_sheet=None): """Update custom css style sheets.""" if style_sheet is None: self.style_sheet = load_style_sheet() else: self.style_sheet = style_sheet self.setStyleSheet(self.style_sheet) self.setMinimumWidth(SASS_VARIABLES.WIDGET_CHANNEL_DIALOG_WIDTH) try: self.list.update_style_sheet(style_sheet) except Exception: pass def update_api(self, worker, api_info, error): """Update api info.""" self._conda_url = api_info.get('conda_url', 'https://conda.anaconda.org') self._conda_url_setup_ready = True if self._setup_ready: self.sig_setup_ready.emit() def setup(self, worker, conda_config_data, error): """Setup the channels widget.""" self.config_sources = conda_config_data.get('config_sources') self.button_add.setDisabled(False) for source, data in self.config_sources.items(): channels = data.get('channels', []) for channel in channels: item = ListWidgetItemChannel(channel=channel, location=source) item.set_editable(False) self.list.addItem(item) self.set_tab_order() self.button_add.setFocus() self.button_ok.setDefault(True) self.button_cancel.setEnabled(True) self.initial_sources = self.list.sources.copy() self.update_status() self._setup_ready = True if self._conda_url_setup_ready: self.sig_setup_ready.emit() def set_tab_order(self): """Fix the tab ordering in the list.""" if self.list._items: self.setTabOrder(self.button_add, self.list._items[0].button_remove) self.setTabOrder(self.list._items[-1].button_remove, self.button_cancel) self.setTabOrder(self.button_cancel, self.button_ok) self.refresh() def add_channel(self): """Add new conda channel.""" user_rc_path = self.api._conda_api.user_rc_path item = ListWidgetItemChannel(channel='', location=user_rc_path) self.list.addItem(item) self.refresh(False) def update_channels(self): """Update channels list and status.""" sources = self.list.sources original = self._group_sources_and_channels(self.initial_sources) updated = self._group_sources_and_channels(sources) if sorted(original) != sorted(updated): self.sig_channels_updated.emit(*self.sources) self.accept() else: self.reject() def refresh(self, channel_status=True): """Update enable/disable status based on item count.""" self.button_add.setEnabled(channel_status and bool(self.list.count)) self.button_ok.setEnabled(channel_status) self.button_cancel.setEnabled(True) if self.list.count() == 0: self.button_add.setEnabled(True) self.button_ok.setEnabled(False) def update_status(self, action='', message='', value=None, max_value=None): """Update the status and progress bar of the widget.""" visible = bool(action) self.label_status.setText(action) self.label_status.setVisible(visible) if value is not None and max_value is not None: self.progress_bar.setVisible(True) self.progress_bar.setRange(0, max_value) self.progress_bar.setValue(value) else: self.progress_bar.setVisible(False) @property def sources(self): """Return sources to add and remove from config.""" original = self._group_sources_and_channels(self.initial_sources) updated = self._group_sources_and_channels(self.list.sources) original = set(original) updated = set(updated) add = updated - original remove = original - updated return add, remove
class MainDialog(DialogBase): """Main dialog for the anaconda navgator updater.""" # Signals sig_application_updated = Signal() sig_ready = Signal() # Class variables PACKAGE = 'anaconda-navigator' WIDTH = 450 HEIGHT = 200 def __init__(self, latest_version=None, prefix=None): """Main dialog for the anaconda navgator updater.""" super(MainDialog, self).__init__() # Variables self.api = CondaAPI() self.prefix = prefix or os.environ.get('CONDA_PREFIX', self.api.ROOT_PREFIX) self.info = {} self.first_run = True self.setup_ready = False self.busy = False self.up_to_date = False self.error = False self.success = False self.status = '' self.current_version = None self.latest_version = latest_version self.style_sheet = load_style_sheet() self.timer = QTimer() self.timer_2 = QTimer() self._windows_appusermodelid = None # Widgets self.message_box = None # For testing self.label_icon = QSvgWidget() self.label_message = LabelBase( "There's a new version of Anaconda Navigator available. " "We strongly recommend you to update.") self.label_status = LabelBase('') self.progress_bar = QProgressBar() self.button_cancel = ButtonNormal('Dismiss') self.button_update = ButtonPrimary('Update now') self.button_launch = ButtonPrimary('Launch Navigator') # Widgets setup if WIN: self._windows_appusermodelid = set_windows_appusermodelid() self.setMinimumSize(self.WIDTH, self.HEIGHT) self.label_message.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.label_message.setWordWrap(True) self.label_status.setWordWrap(True) self.button_update.setAutoDefault(True) self.button_launch.setAutoDefault(True) self.button_cancel.setFocusPolicy(Qt.NoFocus) self.timer.setInterval(1000) self.timer_2.setInterval(5000) self.progress_bar.setTextVisible(False) self.progress_bar.setStyleSheet(self.style_sheet) self.label_icon.load(images.ANACONDA_LOGO) self.label_icon.setMaximumSize(QSize(64, 64)) self.label_icon.setMinimumSize(QSize(64, 64)) self.setWindowTitle('Anaconda Navigator Updater') self.progress_bar.setMaximumWidth(self.WIDTH / 3) self.setMinimumWidth(self.WIDTH) self.setMaximumWidth(self.WIDTH) self.setMinimumHeight(self.HEIGHT) # Layouts layout_status = QHBoxLayout() layout_status.addWidget(self.label_status) layout_status.addWidget(SpacerHorizontal()) layout_status.addWidget(self.progress_bar) layout_text = QVBoxLayout() layout_text.addWidget(self.label_message) layout_text.addStretch() layout_text.addWidget(SpacerVertical()) layout_text.addLayout(layout_status) layout_icon = QVBoxLayout() layout_icon.addWidget(self.label_icon) layout_icon.addStretch() layout_top = QHBoxLayout() layout_top.addLayout(layout_icon) layout_top.addWidget(SpacerHorizontal()) layout_top.addLayout(layout_text) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_update) layout_buttons.addWidget(self.button_launch) layout = QVBoxLayout() layout.addLayout(layout_top) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addStretch() layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_update.clicked.connect(self.install_update) self.button_cancel.clicked.connect(self.reject) self.button_launch.clicked.connect(self.launch) self.timer.timeout.connect(self.refresh) self.timer_2.timeout.connect(self.check_conditions) # Setup self.timer.start() self.timer_2.start() self.check_conditions() self.refresh() def check_conditions(self): """Check every 5 seconds installed packages in case codna was used.""" packages = self.api.linked(prefix=self.prefix) package = [p for p in packages if self.PACKAGE in p] if package: n, v, b = self.api.split_canonical_name(package[0]) self.current_version = v else: self.current_version = None if self.latest_version is None: worker_search = self.api.search(self.PACKAGE, platform=self.api.get_platform()) worker_search.sig_finished.connect(self._search_callback) else: worker = self.api.info() worker.sig_finished.connect(self.setup) self.check_versions() def check_versions(self): """Check if navigator is up to date.""" if self.latest_version and self.current_version: from distutils.version import LooseVersion cur_ver = LooseVersion(self.current_version) lat_ver = LooseVersion(self.latest_version) self.up_to_date = cur_ver >= lat_ver else: self.up_to_date = False def _search_callback(self, worker, output, error): """Setup the widget.""" if isinstance(output, dict): packages = output.get(self.PACKAGE, []) versions = [package.get('version') for package in packages] unique_versions = [] for version in versions: if version not in unique_versions: unique_versions.append(version) if unique_versions: self.latest_version = unique_versions[-1] self.check_versions() worker = self.api.info() worker.sig_finished.connect(self.setup) self.refresh() def setup(self, worker, info, error): """Setup the widget.""" self.info = info self.setup_ready = True self.sig_ready.emit() self.refresh() if self.button_update.isVisible(): self.button_update.setFocus() if self.button_launch.isVisible(): self.button_launch.setFocus() def update_style_sheet(self): """Update custom CSS style sheet.""" self.style_sheet = load_style_sheet() self.setStyleSheet(self.style_sheet) def refresh(self): """Refresh enabled/disabled status of widgets.""" current_version = 'Not installed' if self.current_version: current_version = self.current_version latest_version = '-' if self.latest_version: latest_version = self.latest_version main_message = ( "Current version: <i>{0}</i><br>" "Available version: <b>{1}</b><br>").format( current_version, latest_version) message = self.status running = self.check_running() self.button_launch.setVisible(False) if not self.setup_ready: self.button_update.setDisabled(True) self.progress_bar.setVisible(True) message = 'Updating index...' self.update_status(message) elif self.busy: self.button_update.setDisabled(True) self.progress_bar.setVisible(True) else: self.progress_bar.setVisible(False) if running: message = 'Please close Anaconda Navigator before updating.' self.button_update.setDisabled(running) elif not running: self.button_update.setDisabled(False) if self.success and self.current_version: message = 'Anaconda Navigator was updated successfully.' self.button_update.setVisible(False) self.button_launch.setVisible(True) elif self.up_to_date: message = 'Anaconda Navigator is already up to date.' self.button_update.setVisible(False) self.button_launch.setVisible(True) elif not self.error: self.button_update.setVisible(True) if self.current_version: message = ('An update for Anaconda Navigator is now ' 'available.') self.button_update.setText('Update now') else: message = ( 'Anaconda Navigator is available for install.') self.button_update.setText('Install now') if self.error: self.button_update.setDisabled(False) message = 'Cannot update Anaconda Navigator, <b>{0}</b>' message = message.format(self.error) self.label_status.setText(message) self.label_message.setText(main_message) def update_status(self, status='', value=-1, max_val=-1): """Update progress bar and message status.""" if status: self.status = status self.label_status.setText(status) if value < 0 and max_val < 0: self.progress_bar.setRange(0, 0) else: self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(max_val) self.progress_bar.setValue(value) def check_running(self): """Check if Anaconda Navigator is running.""" # Create file lock lock = filelock.FileLock(NAVIGATOR_LOCKFILE) try: running = False with lock.acquire(timeout=0.01): pass except filelock.Timeout: running = True return running # --- Conda actions and helpers # ------------------------------------------------------------------------- def partial_output_ready(self, worker, output, error): """Handle conda partial output ready.""" self.busy = True # print(type(output)) # print(output) # Get errors and data from ouput if it exists fetch = None if output and isinstance(output, dict): fetch = output.get('fetch') max_val = output.get('maxval', -1) value = output.get('progress', -1) if fetch: status = 'Fetching <b>{0}</b>...'.format(fetch) self.update_status(status=status, max_val=max_val, value=value) def output_ready(self, worker, output, error): """Handle conda output ready.""" self.check_conditions() # Get errors and data from ouput if it exists error_text = output.get('error', '') exception_type = output.get('exception_type', '') exception_name = output.get('exception_name', '') success = output.get('success') actions = output.get('actions', {}) # op_order = output.get('op_order', []) # action_check_fetch = actions.get('CHECK_FETCH', []) # action_rm_fetch = actions.get('RM_FETCHED', []) # action_fetch = actions.get('FETCH', []) # action_check_extract = actions.get('CHECK_EXTRACT', []) # action_rm_extract = actions.get('RM_EXTRACTED', []) # action_extract = actions.get('EXTRACT', []) # action_unlink = actions.get('UNLINK', []) action_link = actions.get('LINK', []) # action_symlink_conda = actions.get('SYMLINK_CONDA', []) self.busy = False # Get errors from json output if error_text or exception_type or exception_name or not success: self.error = exception_name self.success = False self.up_to_date = False elif success and action_link: self.sig_application_updated.emit() self.error = None self.success = True self.up_to_date = False elif success: self.success = False self.error = None self.up_to_date = True worker.lock.release() self.refresh() def install_update(self): """Install the specified version or latest version of navigator.""" self.busy = True self.refresh() # conda_prefix = self.info.et('conda_prefix') # root_prefix = self.info.et('root_prefix') navigator_prefixes = [ # os.path.join(self.api.ROOT_PREFIX, 'envs', '_navigator_'), # os.path.join(self.api.ROOT_PREFIX, 'envs', '_conda_'), self.prefix, ] for prefix in navigator_prefixes: if self.api.environment_exists(prefix=prefix): break if self.latest_version: pkgs = ['{0}=={1}'.format(self.PACKAGE, self.latest_version)] else: pkgs = [self.PACKAGE.format(self.latest_version)] # Lock Navigator lock = filelock.FileLock(NAVIGATOR_LOCKFILE) lock.acquire() worker = self.api.install(prefix=prefix, pkgs=pkgs) worker.lock = lock worker.sig_partial.connect(self.partial_output_ready) worker.sig_finished.connect(self.output_ready) self.refresh() if self.prefix == self.api.ROOT_PREFIX: name = 'root' else: name = os.path.basename(self.prefix) self.button_launch.setFocus() if self.current_version: msg = 'Updating package on <b>{0}</b>...'.format(name) else: msg = 'Installing package on <b>{0}</b>...'.format(name) self.update_status(msg) def launch(self): """Launch Anaconda Navigator.""" leave_path_alone = True root_prefix = self.api.ROOT_PREFIX prefix = self.prefix command = ['anaconda-navigator'] # Use the app bundle on OSX if MAC: command = ['open', os.path.join(prefix, 'Anaconda-Navigator.app')] launch_cmd( prefix, command, leave_path_alone, package_name='anaconda-navigator-app', root_prefix=root_prefix, ) self.close() # --- Qt Overrides # ------------------------------------------------------------------------- def reject(self): """Override Qt method.""" if self.busy: msg_box = MessageBoxQuestion(title='Quit Navigator Updater?', text='Anaconda Navigator is being ' 'updated. <br><br>' 'Are you sure you want to quit?') if msg_box.exec_(): super(MainDialog, self).reject() else: super(MainDialog, self).reject()
class WidgetGallery(QWidget): def __init__(self, parent=None): super(WidgetGallery, self).__init__(parent) self.originalPalette = QApplication.palette() styleComboBox = QComboBox() styleComboBox.addItems(QStyleFactory.keys()) styleLabel = QLabel("&Style:") styleLabel.setBuddy(styleComboBox) self.useStylePaletteCheckBox = QCheckBox( "&Use style's standard palette") self.useStylePaletteCheckBox.setChecked(True) disableWidgetsCheckBox = QCheckBox("&Disable widgets") self.createTopLeftGroupBox() self.createTopRightGroupBox() self.createBottomLeftTabWidget() self.createBottomRightGroupBox() self.createProgressBar() styleComboBox.activated[str].connect(self.changeStyle) self.useStylePaletteCheckBox.toggled.connect(self.changePalette) disableWidgetsCheckBox.toggled.connect( self.topLeftGroupBox.setDisabled) disableWidgetsCheckBox.toggled.connect( self.topRightGroupBox.setDisabled) disableWidgetsCheckBox.toggled.connect( self.bottomLeftTabWidget.setDisabled) disableWidgetsCheckBox.toggled.connect( self.bottomRightGroupBox.setDisabled) topLayout = QHBoxLayout() topLayout.addWidget(styleLabel) topLayout.addWidget(styleComboBox) topLayout.addStretch(1) topLayout.addWidget(self.useStylePaletteCheckBox) topLayout.addWidget(disableWidgetsCheckBox) mainLayout = QGridLayout() mainLayout.addLayout(topLayout, 0, 0, 1, 2) mainLayout.addWidget(self.topLeftGroupBox, 1, 0) mainLayout.addWidget(self.topRightGroupBox, 1, 1) mainLayout.addWidget(self.bottomLeftTabWidget, 2, 0) mainLayout.addWidget(self.bottomRightGroupBox, 2, 1) mainLayout.addWidget(self.progressBar, 3, 0, 1, 2) mainLayout.setRowStretch(1, 1) mainLayout.setRowStretch(2, 1) mainLayout.setColumnStretch(0, 1) mainLayout.setColumnStretch(1, 1) self.setLayout(mainLayout) self.changeStyle('Windows') self.val = 0 def changeStyle(self, styleName): QApplication.setStyle(QStyleFactory.create(styleName)) self.changePalette() def changePalette(self): if (self.useStylePaletteCheckBox.isChecked()): QApplication.setPalette(QApplication.style().standardPalette()) else: QApplication.setPalette(self.originalPalette) def advanceProgressBar(self): self.progressBar.setMaximum(100) self.val += 1 self.progressBar.setValue(self.val) def createTopLeftGroupBox(self): self.topLeftGroupBox = QGroupBox("Group 1") radioButton1 = QRadioButton("Radio button 1") radioButton2 = QRadioButton("Radio button 2") radioButton3 = QRadioButton("Radio button 3") radioButton1.setChecked(True) checkBox = QCheckBox("Tri-state check box") checkBox.setTristate(True) checkBox.setCheckState(Qt.PartiallyChecked) layout = QVBoxLayout() layout.addWidget(radioButton1) layout.addWidget(radioButton2) layout.addWidget(radioButton3) layout.addWidget(checkBox) layout.addStretch(1) self.topLeftGroupBox.setLayout(layout) 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) def createBottomLeftTabWidget(self): self.bottomLeftTabWidget = QTabWidget() self.bottomLeftTabWidget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Ignored) tab1 = QWidget() tableWidget = QTableWidget(10, 10) tab1hbox = QHBoxLayout() tab1hbox.setContentsMargins(5, 5, 5, 5) tab1hbox.addWidget(tableWidget) tab1.setLayout(tab1hbox) tab2 = QWidget() textEdit = QTextEdit() textEdit.setPlainText("Twinkle, twinkle, little star,\n" "How I wonder what you are.\n" "Up above the world so high,\n" "Like a diamond in the sky.\n" "Twinkle, twinkle, little star,\n" "How I wonder what you are!\n") tab2hbox = QHBoxLayout() tab2hbox.setContentsMargins(5, 5, 5, 5) tab2hbox.addWidget(textEdit) tab2.setLayout(tab2hbox) self.bottomLeftTabWidget.addTab(tab1, "&Table") self.bottomLeftTabWidget.addTab(tab2, "Text &Edit") def createBottomRightGroupBox(self): self.bottomRightGroupBox = QGroupBox("Group 3") self.bottomRightGroupBox.setCheckable(True) self.bottomRightGroupBox.setChecked(True) lineEdit = QLineEdit('s3cRe7') lineEdit.setEchoMode(QLineEdit.Password) spinBox = QSpinBox(self.bottomRightGroupBox) spinBox.setValue(50) dateTimeEdit = QDateTimeEdit(self.bottomRightGroupBox) dateTimeEdit.setDateTime(QDateTime.currentDateTime()) slider = QSlider(Qt.Horizontal, self.bottomRightGroupBox) slider.setValue(40) scrollBar = QScrollBar(Qt.Horizontal, self.bottomRightGroupBox) scrollBar.setValue(60) dial = QDial(self.bottomRightGroupBox) dial.setValue(30) dial.setNotchesVisible(True) layout = QGridLayout() layout.addWidget(lineEdit, 0, 0, 1, 2) layout.addWidget(spinBox, 1, 0, 1, 2) layout.addWidget(dateTimeEdit, 2, 0, 1, 2) layout.addWidget(slider, 3, 0) layout.addWidget(scrollBar, 4, 0) layout.addWidget(dial, 3, 1, 2, 1) layout.setRowStretch(5, 1) self.bottomRightGroupBox.setLayout(layout) def createProgressBar(self): self.progressBar = QProgressBar() self.progressBar.setRange(0, 10000) self.progressBar.setValue(0) timer = QTimer(self) timer.timeout.connect(self.advanceProgressBar) timer.start(1000)
class BatchProcessingView(View): btn_run_batch: QPushButton progress_bar: QProgressBar field_channel: QLineEdit field_workflow_config: FileInput field_input_dir: FileInput field_output_dir: FileInput def __init__(self, controller: IBatchProcessingController): super().__init__(template_class=MainTemplate) if controller is None: raise ValueError("controller") self._controller = controller self.setObjectName("batchProcessingView") def load(self, model=None): self._setup_ui() def _setup_ui(self): """ Set up the UI for the BatchProcessingView """ layout = QVBoxLayout() self.setLayout(layout) # Workflow config self.field_workflow_config = FileInput( mode=FileInputMode.FILE, filter="Json file (*.json)", placeholder_text="Load a JSON workflow file..." ) self.field_workflow_config.file_selected.connect(self._form_field_changed) row1 = FormRow("1. Load workflow:", self.field_workflow_config) # Channel index self.field_channel = QLineEdit("0") self.field_channel.setValidator(QIntValidator(bottom=0)) self.field_channel.textChanged.connect(self._form_field_changed) row2 = FormRow("2. Structure channel index:", self.field_channel) # Input dir self.field_input_dir = FileInput(mode=FileInputMode.DIRECTORY, placeholder_text="Select a directory...") self.field_input_dir.file_selected.connect(self._form_field_changed) row3 = FormRow("3. Input directory:", self.field_input_dir) # Output dir self.field_output_dir = FileInput(mode=FileInputMode.DIRECTORY, placeholder_text="Select a directory...") self.field_output_dir.file_selected.connect(self._form_field_changed) row4 = FormRow("4. Output directory:", self.field_output_dir) # Help label = QLabel() label.setText("Supported file formats: .tif, .tiff, .czi, .ome.tif, .ome.tiff") form = QWidget() form.setLayout(Form([row1, row2, row3, row4])) layout.addWidget(form) layout.addWidget(label) # Submit self.btn_run_batch = QPushButton("Run Batch") self.btn_run_batch.clicked.connect(self._btn_run_batch_clicked) self.update_button(enabled=False) layout.addWidget(self.btn_run_batch) # Progress bar self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) self.progress_bar.setValue(0) self.progress_bar.setTextVisible(True) self.progress_bar.setVisible(False) layout.addWidget(self.progress_bar) def update_button(self, enabled: bool): """ Update state of process button Inputs: enabled: True to enable the button, false to disable it """ self.btn_run_batch.setEnabled(enabled) def set_run_batch_in_progress(self): """ Update page to reflect that a batch run is in progress """ # TODO make a CancelButton widget to avoid repeating this connect / disconnect pattern self.btn_run_batch.setText("Cancel") self.btn_run_batch.clicked.disconnect() self.btn_run_batch.clicked.connect(self._btn_run_batch_cancel_clicked) self.progress_bar.setVisible(True) def reset_run_batch(self): """ Reset page state to reflect that there is no batch run in progress """ self.progress_bar.setValue(0) self.btn_run_batch.setText("Run Batch") self.btn_run_batch.clicked.disconnect() self.btn_run_batch.clicked.connect(self._btn_run_batch_clicked) self.progress_bar.setVisible(False) def set_progress(self, progress: int): """ Update progress bar Inputs: progress (int): numerical value to set the progress bar to """ self.progress_bar.setValue(progress) ##################################################################### # Event handlers ##################################################################### def _btn_run_batch_clicked(self): self._controller.run_batch() def _btn_run_batch_cancel_clicked(self): self.btn_run_batch.setText("Canceling...") self._controller.cancel_run_batch() def _form_field_changed(self, value): workflow_config = self.field_workflow_config.selected_file channel_index = int(self.field_channel.text()) if self.field_channel.text() else None input_dir = self.field_input_dir.selected_file output_dir = self.field_output_dir.selected_file self._controller.update_batch_parameters(workflow_config, channel_index, input_dir, output_dir)
class AlgorithmOptions(QWidget): def __init__(self, settings: StackSettings, image_view: StackImageView): super().__init__() self.settings = settings self.view_name = image_view.name self.show_result = QEnumComboBox( enum_class=LabelEnum) # QCheckBox("Show result") self._set_show_label_from_settings() self.opacity = QDoubleSpinBox() self.opacity.setRange(0, 1) self.opacity.setSingleStep(0.1) self._set_opacity_from_settings() self.only_borders = QCheckBox("Only borders") self._set_border_mode_from_settings() self.borders_thick = QSpinBox() self.borders_thick.setRange(1, 25) self.borders_thick.setSingleStep(1) self._set_border_thick_from_settings() self.execute_in_background_btn = QPushButton("Execute in background") self.execute_in_background_btn.setToolTip( "Run calculation in background. Put result in multiple files panel" ) self.execute_btn = QPushButton("Execute") self.execute_btn.setStyleSheet("QPushButton{font-weight: bold;}") self.execute_all_btn = QPushButton("Execute all") self.execute_all_btn.setToolTip( "Execute in batch mode segmentation with current parameter. File list need to be specified in image tab." ) self.execute_all_btn.setDisabled(True) self.save_parameters_btn = QPushButton("Save parameters") self.block_execute_all_btn = False self.algorithm_choose_widget = AlgorithmChoose(settings, mask_algorithm_dict) self.algorithm_choose_widget.result.connect(self.execution_result_set) self.algorithm_choose_widget.finished.connect(self.execution_finished) self.algorithm_choose_widget.progress_signal.connect( self.progress_info) # self.stack_layout = QStackedLayout() self.keep_chosen_components_chk = QCheckBox("Save selected components") self.keep_chosen_components_chk.setToolTip( "Save chosen components when loading segmentation form file\n or from multiple file widget." ) self.keep_chosen_components_chk.stateChanged.connect( self.set_keep_chosen_components) self.keep_chosen_components_chk.setChecked( settings.keep_chosen_components) self.show_parameters = QPushButton("Show parameters") self.show_parameters.setToolTip( "Show parameters of segmentation for each components") self.show_parameters_widget = SegmentationInfoDialog( self.settings, self.algorithm_choose_widget.change_algorithm) self.show_parameters.clicked.connect(self.show_parameters_widget.show) self.choose_components = ChosenComponents() self.choose_components.check_change_signal.connect( image_view.refresh_selected) self.choose_components.mouse_leave.connect(image_view.component_unmark) self.choose_components.mouse_enter.connect(image_view.component_mark) # WARNING works only with one channels algorithms # SynchronizeValues.add_synchronization("channels_chose", widgets_list) self.chosen_list = [] self.progress_bar2 = QProgressBar() self.progress_bar2.setHidden(True) self.progress_bar = QProgressBar() self.progress_bar.setHidden(True) self.progress_info_lab = QLabel() self.progress_info_lab.setHidden(True) self.file_list = [] self.batch_process = BatchProceed() self.batch_process.progress_signal.connect(self.progress_info) self.batch_process.error_signal.connect(self.execution_all_error) self.batch_process.execution_done.connect(self.execution_all_done) self.batch_process.range_signal.connect(self.progress_bar.setRange) self.is_batch_process = False self.setContentsMargins(0, 0, 0, 0) main_layout = QVBoxLayout() # main_layout.setSpacing(0) opt_layout = QHBoxLayout() opt_layout.setContentsMargins(0, 0, 0, 0) opt_layout.addWidget(self.show_result) opt_layout.addWidget(right_label("Opacity:")) opt_layout.addWidget(self.opacity) main_layout.addLayout(opt_layout) opt_layout2 = QHBoxLayout() opt_layout2.setContentsMargins(0, 0, 0, 0) opt_layout2.addWidget(self.only_borders) opt_layout2.addWidget(right_label("Border thick:")) opt_layout2.addWidget(self.borders_thick) main_layout.addLayout(opt_layout2) btn_layout = QGridLayout() btn_layout.setContentsMargins(0, 0, 0, 0) btn_layout.addWidget(self.execute_btn, 0, 0) btn_layout.addWidget(self.execute_in_background_btn, 0, 1) btn_layout.addWidget(self.execute_all_btn, 1, 0) btn_layout.addWidget(self.save_parameters_btn, 1, 1) main_layout.addLayout(btn_layout) main_layout.addWidget(self.progress_bar2) main_layout.addWidget(self.progress_bar) main_layout.addWidget(self.progress_info_lab) main_layout.addWidget(self.algorithm_choose_widget, 1) # main_layout.addWidget(self.algorithm_choose) # main_layout.addLayout(self.stack_layout, 1) main_layout.addWidget(self.choose_components) down_layout = QHBoxLayout() down_layout.addWidget(self.keep_chosen_components_chk) down_layout.addWidget(self.show_parameters) main_layout.addLayout(down_layout) main_layout.addStretch() main_layout.setContentsMargins(0, 0, 0, 0) # main_layout.setSpacing(0) self.setLayout(main_layout) # noinspection PyUnresolvedReferences self.execute_in_background_btn.clicked.connect( self.execute_in_background) self.execute_btn.clicked.connect(self.execute_action) self.execute_all_btn.clicked.connect(self.execute_all_action) self.save_parameters_btn.clicked.connect(self.save_parameters) # noinspection PyUnresolvedReferences self.opacity.valueChanged.connect(self._set_opacity) # noinspection PyUnresolvedReferences self.show_result.currentEnumChanged.connect(self._set_show_label) self.only_borders.stateChanged.connect(self._set_border_mode) # noinspection PyUnresolvedReferences self.borders_thick.valueChanged.connect(self._set_border_thick) image_view.component_clicked.connect( self.choose_components.other_component_choose) settings.chosen_components_widget = self.choose_components settings.components_change_list.connect( self.choose_components.new_choose) settings.image_changed.connect( self.choose_components.remove_components) settings.connect_to_profile( f"{self.view_name}.image_state.only_border", self._set_border_mode_from_settings) settings.connect_to_profile( f"{self.view_name}.image_state.border_thick", self._set_border_thick_from_settings) settings.connect_to_profile(f"{self.view_name}.image_state.opacity", self._set_opacity_from_settings) settings.connect_to_profile(f"{self.view_name}.image_state.show_label", self._set_show_label_from_settings) def _set_border_mode(self, value: bool): self.settings.set_in_profile( f"{self.view_name}.image_state.only_border", value) def _set_border_thick(self, value: int): self.settings.set_in_profile( f"{self.view_name}.image_state.border_thick", value) def _set_opacity(self, value: float): self.settings.set_in_profile(f"{self.view_name}.image_state.opacity", value) def _set_show_label(self, value: LabelEnum): self.settings.set_in_profile( f"{self.view_name}.image_state.show_label", value) def _set_border_mode_from_settings(self): self.only_borders.setChecked( self.settings.get_from_profile( f"{self.view_name}.image_state.only_border", True)) def _set_border_thick_from_settings(self): self.borders_thick.setValue( self.settings.get_from_profile( f"{self.view_name}.image_state.border_thick", 1)) def _set_opacity_from_settings(self): self.opacity.setValue( self.settings.get_from_profile( f"{self.view_name}.image_state.opacity", 1.0)) def _set_show_label_from_settings(self): self.show_result.setCurrentEnum( self.settings.get_from_profile( f"{self.view_name}.image_state.show_label", LabelEnum.Show_results)) @Slot(int) def set_keep_chosen_components(self, val): self.settings.set_keep_chosen_components(val) def save_parameters(self): dial = PSaveDialog(io_functions.save_parameters_dict, system_widget=False, settings=self.settings, path=IO_SAVE_DIRECTORY) if not dial.exec_(): return res = dial.get_result() res.save_class.save(res.save_destination, self.algorithm_choose_widget.current_parameters()) def file_list_change(self, val): self.file_list = val if len(self.file_list) > 0 and not self.block_execute_all_btn: self.execute_all_btn.setEnabled(True) else: self.execute_all_btn.setDisabled(True) def get_chosen_components(self): return sorted(self.choose_components.get_chosen()) @property def segmentation(self): return self.settings.roi @segmentation.setter def segmentation(self, val): self.settings.roi = val def _image_changed(self): self.settings.roi = None self.choose_components.set_chose([], []) def _execute_in_background_init(self): if self.batch_process.isRunning(): return self.progress_bar2.setVisible(True) self.progress_bar2.setRange(0, self.batch_process.queue.qsize()) self.progress_bar2.setValue(self.batch_process.index) self.progress_bar.setVisible(True) self.progress_bar.setValue(0) self.execute_btn.setDisabled(True) self.batch_process.start() def execute_in_background(self): # TODO check if components are properly passed widget = self.algorithm_choose_widget.current_widget() segmentation_profile = widget.get_segmentation_profile() task = BatchTask(self.settings.get_project_info(), segmentation_profile, None) self.batch_process.add_task(task) self.progress_bar2.setRange(0, self.progress_bar2.maximum() + 1) self._execute_in_background_init() def execute_all_action(self): dial = PSaveDialog( SaveROI, settings=self.settings, system_widget=False, path="io.save_batch", file_mode=PSaveDialog.Directory, ) if not dial.exec_(): return folder_path = str(dial.selectedFiles()[0]) widget = self.algorithm_choose_widget.current_widget() save_parameters = dial.values segmentation_profile = widget.get_segmentation_profile() for file_path in self.file_list: task = BatchTask(file_path, segmentation_profile, (folder_path, save_parameters)) self.batch_process.add_task(task) self.progress_bar2.setRange( 0, self.progress_bar2.maximum() + len(self.file_list)) self._execute_in_background_init() def execution_all_error(self, text): QMessageBox.warning(self, "Proceed error", text) def execution_all_done(self): if not self.batch_process.queue.empty(): self._execute_in_background_init() return self.execute_btn.setEnabled(True) self.block_execute_all_btn = False if len(self.file_list) > 0: self.execute_all_btn.setEnabled(True) self.progress_bar.setHidden(True) self.progress_bar2.setHidden(True) self.progress_info_lab.setHidden(True) def execute_action(self): self.execute_btn.setDisabled(True) self.execute_all_btn.setDisabled(True) self.block_execute_all_btn = True self.is_batch_process = False self.progress_bar.setRange(0, 0) self.choose_components.setDisabled(True) chosen = sorted(self.choose_components.get_chosen()) blank = get_mask(self.settings.roi, self.settings.mask, chosen) if blank is not None: # Problem with handling time data in algorithms # TODO Fix This blank = blank[0] self.progress_bar.setHidden(False) widget: AlgorithmSettingsWidget = self.algorithm_choose_widget.current_widget( ) widget.set_mask(blank) self.progress_bar.setRange(0, widget.algorithm.get_steps_num()) widget.execute() self.chosen_list = chosen def progress_info(self, text, num, file_name="", file_num=0): self.progress_info_lab.setVisible(True) if file_name != "": self.progress_info_lab.setText(file_name + "\n" + text) else: self.progress_info_lab.setText(text) self.progress_bar.setValue(num) self.progress_bar2.setValue(file_num) def execution_finished(self): self.execute_btn.setEnabled(True) self.block_execute_all_btn = False if len(self.file_list) > 0: self.execute_all_btn.setEnabled(True) self.progress_bar.setHidden(True) self.progress_info_lab.setHidden(True) self.choose_components.setDisabled(False) def execution_result_set(self, result): self.settings.set_segmentation_result(result) def showEvent(self, _): widget = self.algorithm_choose_widget.current_widget() widget.image_changed(self.settings.image)
class EmailReport(QWidget): emailSentSignal = Signal() def __init__(self, info='', report='', toemail=None, fromemail=None, password=None, server=None, port=25, subject='Report', parent=None): super().__init__(parent=parent) self._info = str(info) # infomation reported self._report = str(report) # report self._toemail = str(toemail) self._subject = str(subject) mainLayout = QVBoxLayout(self) mainLayout.setContentsMargins(0, 0, 0, 0) mainLayout.setSpacing(0) self.setStyleSheet("background-color: white;") # information self.infoText = QLabel(self._info) self.infoText.setWordWrap(True) self.infoText.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.infoText.setFont(QFont('Mcorsoft YaHei', 11, 50)) self.infoText.setStyleSheet("color:rgb(130,130,130); padding:10px;") self.infoText.setFrameStyle(QFrame.Box|QFrame.Raised) # report self.reportText = QLabel(self._report) self.reportText.setWordWrap(True) self.reportText.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.reportText.setFont(QFont('Mcorsoft YaHei', 11, 50)) self.reportText.setStyleSheet("color:rgb(130,130,130); padding:10px;") self.reportText.setFrameStyle(QFrame.Box|QFrame.Raised) self.reportText.hide() # 邮件地址 self.emailWidget = Email(email=fromemail, password=password, server=server, port=port, parent=self) # 邮件发送按钮 buttonLayout = QHBoxLayout() buttonLayout.setContentsMargins(10, 10, 10, 10) buttonLayout.setSpacing(0) buttonLayout.setAlignment(Qt.AlignHCenter) emailSendButton = QPushButton() emailSendButton.setText("发送邮件") # 绑定点击事件处理函数 emailSendButton.clicked.connect(self.sendEmail) emailSendButton.setStyleSheet( "QPushButton{font:63 11pt 微软雅黑;border:0px; border-radius:3px; color:#FFFFFF; background-color:#2278C6}" "QPushButton:hover{font:63 11pt 微软雅黑;border:0px; border-radius:3px;background-color:#1891FF;}" ) buttonLayout.addWidget(emailSendButton) # 进度条 self.progressBar = QProgressBar(self) self.progressBar.setRange(0, 10) self.progressBar.setTextVisible(False) self.progressBar.setMaximumHeight(5) # 将控件放入布局中 mainLayout.addWidget(self.infoText) mainLayout.addWidget(self.reportText) mainLayout.addWidget(self.emailWidget) mainLayout.addLayout(buttonLayout) mainLayout.addWidget(self.progressBar) def sendEmail(self): successFlag = True subject = self.subject() report = self.report() toemail = self.toemail() server = self.server() port = self.port() fromemail = self.fromemail() password = self.password() self.progressBar.setValue(2) if successFlag: try: message = MIMEText(report, 'plain', 'utf-8') message['From'] = Header(formataddr(('Reporter', fromemail)), 'utf-8') # 发送者 message['To'] = Header(formataddr(('Handler', toemail)), 'utf-8') # 接收者 message['Subject'] = Header(subject, 'utf-8') self.progressBar.setValue(4) except: showQuickMessage(title='error', text='请输入合法的邮箱', icon=QMessageBox.Critical, parent=self) successFlag = False if successFlag: try: with smtplib.SMTP() as smtpObj: smtpObj.connect(server, port) smtpObj.login(fromemail, password) self.progressBar.setValue(6) smtpObj.sendmail(fromemail, [toemail], message.as_string()) self.progressBar.setValue(10) showQuickMessage(title='info', text="邮件发送完成", icon=QMessageBox.Information, parent=self) except: showQuickMessage(title='error', text=textwrap.dedent(""" 邮件发送失败,请检查 1)网络是否连接。 2)邮箱账号密码是否输入正确 3)邮箱是否与服务器对应"""), icon=QMessageBox.Critical, parent=self) successFlag = False if successFlag: self.emailSentSignal.emit() self.progressBar.reset() def subject(self): return self._subject def setSubject(self, subject): self._subject = str(subject) def info(self): return self._info def setInfo(self, info): self._info = str(info) self.infoText.setText(self._info) def report(self): return self._report def setReport(self, report): self._report = str(report) self.reportText.setText(self._report) def setReportVisible(self, visible=True): if visible: self.reportText.show() else: self.reportText.hide() def showReport(self): self.setReportVisible(True) def hideReport(self): self.setReportVisible(False) def server(self): return self.emailWidget.server() def setServer(self, server): self.emailWidget.setServer(server) def port(self): return self.emailWidget.port() def setPort(self, port): self.emailWidget.setPort(port) def fromemail(self): return self.emailWidget.email() def setFromemail(self, email): self.emailWidget.setEmail(email) def toemail(self): return self._toemail def setToemail(self, email): self._toemail = email def password(self): return self.emailWidget.password() def setPassword(self, password): self.emailWidget.setPassword(password)
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 AbortWindow(QDialog): """ Displays busy message and provides abort button. The class serves SmoothCube, WorkerThread and SelectSmoothing. """ def __init__(self, parent=None): """ init abort or notification ui. Displays while smoothing freezes the application. Allows abort button to be added if needed. """ super(AbortWindow, self).__init__(parent) self.setModal(False) self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) self.parent = parent self.label_a_1 = QLabel("Executing smoothing algorithm.") self.label_a_2 = QLabel("This may take several minutes.") self.abort_button = QPushButton("Abort") self.abort_button.clicked.connect(self.abort) self.pb = QProgressBar(self) self.pb_counter = 0 self.abort_flag = False self.info_box = None # vbl is short for Vertical Box Layout vbl = QVBoxLayout() vbl.addWidget(self.label_a_1) vbl.addWidget(self.label_a_2) vbl.addWidget(self.pb) vbl.addWidget(self.abort_button) self.setLayout(vbl) self.show() def init_pb(self, start, end): """ Init the progress bar :param start: Start Value :param end: End Value """ self.pb.setRange(start, end) self.pb_counter = start def update_pb(self): """ This function is called in the worker thread to update the progress bar and checks if the local class variable abort_flag is active. If the abort button is clicked, the main thread will set abort_flag to True. The next time the worker thread calls this function, a custom exception is raised terminating the calculation. The exception is handled by the WorkerThread class. :raises: AbortException: terminating smoothing calculation """ if self.abort_flag: raise AbortException("Abort Calculation") self.pb_counter += 1 self.pb.setValue(self.pb_counter) QApplication.processEvents() def abort(self): """Abort calculation""" self.abort_flag = True self.parent.clean_up() def show_error_message(self, message, title, parent=None): self.info_box = QMessageBox(parent=parent) self.info_box.setIcon(QMessageBox.Information) self.info_box.setText(message) self.info_box.setWindowTitle(title) self.info_box.setStandardButtons(QMessageBox.Ok) self.info_box.show() def smoothing_done(self, component_id=None): """Notify user success""" self.hide() if component_id is None: message = "The result has been added as a" \ " new component of the input Data." \ " The new component can be accessed" \ " in the viewer drop-downs." else: message = "The result has been added as" \ " \"{0}\" and can be selected" \ " in the viewer drop-down menu.".format(component_id) self.show_error_message(message, "Success", self) self.clean_up() def print_error(self, exception): """Print error message""" if "signal only works in main thread" in str(exception): message = "Smoothing Failed!\n\n" + "Please update your SpectralCube package" else: message = "Smoothing Failed!\n\n" + str(exception) self.show_error_message(message, "Error", self) self.clean_up() def clean_up(self): self.parent.clean_up() def keyPressEvent(self, e): if e.key() == Qt.Key_Escape: self.abort()
class MainWindow(QMainWindow): def __init__(self, title): super().__init__() self.setWindowTitle(title) self.lib_path = "" self.final_title = "" analysis_icon = QIcon(os.path.join(icons_dir, "icon.png")) stack_icon = QIcon(os.path.join(icons_dir, "icon_stack.png")) self.analysis_button = QToolButton(self) self.analysis_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.analysis_button.setIcon(analysis_icon) # TODO use more general solution for text wrapping self.analysis_button.setText(SEGMENTATION_NAME.replace(" ", "\n")) self.analysis_button.setIconSize(QSize(100, 100)) self.mask_button = QToolButton(self) self.mask_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mask_button.setIcon(stack_icon) self.mask_button.setText(MASK_NAME.replace(" ", "\n")) self.mask_button.setIconSize(QSize(100, 100)) self.analysis_button.clicked.connect(self.launch_analysis) self.mask_button.clicked.connect(self.launch_mask) self.progress = QProgressBar() self.progress.setHidden(True) layout = QGridLayout() layout.addWidget(self.progress, 0, 0, 1, 2) layout.addWidget(self.analysis_button, 1, 1) layout.addWidget(self.mask_button, 1, 0) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) self.setWindowIcon(analysis_icon) self.prepare = None self.wind = None def _launch_begin(self): self.progress.setVisible(True) self.progress.setRange(0, 0) self.analysis_button.setDisabled(True) self.mask_button.setDisabled(True) import_config() def launch_analysis(self): self._launch_begin() self.lib_path = "PartSeg.segmentation_analysis.main_window" self.final_title = "PartSeg Segmentation Analysis" self.prepare = Prepare(self.lib_path) self.prepare.finished.connect(self.launch) self.prepare.start() def launch_mask(self): self._launch_begin() self.lib_path = "PartSeg.segmentation_mask.main_window" self.final_title = "PartSeg Mask Segmentation" self.prepare = Prepare(self.lib_path) self.prepare.finished.connect(self.launch) self.prepare.start() def window_shown(self): self.close() def launch(self): if self.prepare.result is None: self.close() return if self.prepare.errors: errors_message = QMessageBox() errors_message.setText("There are errors during start") errors_message.setInformativeText( "During load saved state some of data could not be load properly\n" "The files has prepared backup copies in state directory (Help > State directory)" ) errors_message.setStandardButtons(QMessageBox.Ok) text = "\n".join(["File: " + x[0] + "\n" + str(x[1]) for x in self.prepare.errors]) errors_message.setDetailedText(text) errors_message.exec() wind = self.prepare.result(title=self.final_title, signal_fun=self.window_shown) wind.show() self.wind = wind
class AbortWindow(QDialog): """ Displays busy message and provides abort button. The class serves SmoothCube, WorkerThread and SelectSmoothing. """ def __init__(self, parent=None): """ init abort or notification ui. Displays while smoothing freezes the application. Allows abort button to be added if needed. """ super(AbortWindow, self).__init__(parent) self.setModal(False) self.setWindowFlags(self.windowFlags() | Qt.Tool) self.parent = parent self.label_a_1 = QLabel("Executing smoothing algorithm.") self.label_a_2 = QLabel("This may take several minutes.") self.abort_button = QPushButton("Abort") self.abort_button.clicked.connect(self.abort) self.pb = QProgressBar(self) self.pb_counter = 0 self.abort_function = None # vbl is short for Vertical Box Layout vbl = QVBoxLayout() vbl.addWidget(self.label_a_1) vbl.addWidget(self.label_a_2) vbl.addWidget(self.pb) vbl.addWidget(self.abort_button) self.setLayout(vbl) self.show() def init_pb(self, start, end): """ Init the progress bar :param start: Start Value :param end: End Value """ self.pb.setRange(start, end) self.pb_counter = start def update_pb(self): """Update progress bar""" self.pb_counter += 1 self.pb.setValue(self.pb_counter) QApplication.processEvents() def abort(self): """Abort calculation""" if self.abort_function is not None: self.abort_function() self.parent.clean_up() def smoothing_done(self, component_id=None): """Notify user success""" self.hide() if component_id is None: message = "The result has been added as a" \ " new component of the input Data." \ " The new component can be accessed" \ " in the viewer drop-downs." else: message = "The result has been added as" \ " \"{0}\" and can be selected" \ " in the viewer drop-down menu.".format(component_id) info = QMessageBox.information(self, "Success", message) self.clean_up() def print_error(self, exception): """Print error message""" message = "Smoothing Failed!\n\n" + str(exception) info = QMessageBox.critical(self, "Error", message) self.clean_up() def clean_up(self): self.parent.clean_up()