def setup_page(self): interface_group = QGroupBox(_("Interface")) newcb = self.create_checkbox singletab_box = newcb(_("One tab per script"), 'single_tab') showtime_box = newcb(_("Show elapsed time"), 'show_elapsed_time') icontext_box = newcb(_("Show icons and text"), 'show_icontext') # Interface Group interface_layout = QVBoxLayout() interface_layout.addWidget(singletab_box) interface_layout.addWidget(showtime_box) interface_layout.addWidget(icontext_box) interface_group.setLayout(interface_layout) # Source Code Group display_group = QGroupBox(_("Source code")) buffer_spin = self.create_spinbox(_("Buffer: "), _(" lines"), 'max_line_count', min_=0, max_=1000000, step=100, tip=_("Set maximum line count")) wrap_mode_box = newcb(_("Wrap lines"), 'wrap') merge_channels_box = newcb( _("Merge process standard output/error channels"), 'merge_output_channels', tip=_("Merging the output channels of the process means that\n" "the standard error won't be written in red anymore,\n" "but this has the effect of speeding up display.")) colorize_sys_stderr_box = newcb( _("Colorize standard error channel using ANSI escape codes"), 'colorize_sys_stderr', tip=_("This method is the only way to have colorized standard\n" "error channel when the output channels have been " "merged.")) merge_channels_box.toggled.connect(colorize_sys_stderr_box.setEnabled) merge_channels_box.toggled.connect(colorize_sys_stderr_box.setChecked) colorize_sys_stderr_box.setEnabled( self.get_option('merge_output_channels')) display_layout = QVBoxLayout() display_layout.addWidget(buffer_spin) display_layout.addWidget(wrap_mode_box) display_layout.addWidget(merge_channels_box) display_layout.addWidget(colorize_sys_stderr_box) display_group.setLayout(display_layout) # Background Color Group bg_group = QGroupBox(_("Background color")) bg_label = QLabel( _("This option will be applied the next time " "a Python console or a terminal is opened.")) bg_label.setWordWrap(True) lightbg_box = newcb(_("Light background (white color)"), 'light_background') bg_layout = QVBoxLayout() bg_layout.addWidget(bg_label) bg_layout.addWidget(lightbg_box) bg_group.setLayout(bg_layout) # Advanced settings source_group = QGroupBox(_("Source code")) completion_box = newcb(_("Automatic code completion"), 'codecompletion/auto') case_comp_box = newcb(_("Case sensitive code completion"), 'codecompletion/case_sensitive') comp_enter_box = newcb(_("Enter key selects completion"), 'codecompletion/enter_key') calltips_box = newcb(_("Display balloon tips"), 'calltips') source_layout = QVBoxLayout() source_layout.addWidget(completion_box) source_layout.addWidget(case_comp_box) source_layout.addWidget(comp_enter_box) source_layout.addWidget(calltips_box) source_group.setLayout(source_layout) # PYTHONSTARTUP replacement pystartup_group = QGroupBox(_("PYTHONSTARTUP replacement")) pystartup_bg = QButtonGroup(pystartup_group) pystartup_label = QLabel( _("This option will override the " "PYTHONSTARTUP environment variable which\n" "defines the script to be executed during " "the Python console startup.")) def_startup_radio = self.create_radiobutton( _("Default PYTHONSTARTUP script"), 'pythonstartup/default', button_group=pystartup_bg) cus_startup_radio = self.create_radiobutton( _("Use the following startup script:"), 'pythonstartup/custom', button_group=pystartup_bg) pystartup_file = self.create_browsefile('', 'pythonstartup', '', filters=_("Python scripts")+\ " (*.py)") def_startup_radio.toggled.connect(pystartup_file.setDisabled) cus_startup_radio.toggled.connect(pystartup_file.setEnabled) pystartup_layout = QVBoxLayout() pystartup_layout.addWidget(pystartup_label) pystartup_layout.addWidget(def_startup_radio) pystartup_layout.addWidget(cus_startup_radio) pystartup_layout.addWidget(pystartup_file) pystartup_group.setLayout(pystartup_layout) # Monitor Group monitor_group = QGroupBox(_("Monitor")) monitor_label = QLabel( _("The monitor provides introspection " "features to console: code completion, " "calltips and variable explorer. " "Because it relies on several modules, " "disabling the monitor may be useful " "to accelerate console startup.")) monitor_label.setWordWrap(True) monitor_box = newcb(_("Enable monitor"), 'monitor/enabled') for obj in (completion_box, case_comp_box, comp_enter_box, calltips_box): monitor_box.toggled.connect(obj.setEnabled) obj.setEnabled(self.get_option('monitor/enabled')) monitor_layout = QVBoxLayout() monitor_layout.addWidget(monitor_label) monitor_layout.addWidget(monitor_box) monitor_group.setLayout(monitor_layout) # Qt Group opts = [ (_("Default library"), 'default'), ('PyQt5', 'pyqt5'), ('PyQt4', 'pyqt'), ('PySide', 'pyside'), ] qt_group = QGroupBox(_("Qt-Python Bindings")) qt_setapi_box = self.create_combobox( _("Library:") + " ", opts, 'qt/api', default='default', tip=_("This option will act on<br> " "libraries such as Matplotlib, guidata " "or ETS")) qt_layout = QVBoxLayout() qt_layout.addWidget(qt_setapi_box) qt_group.setLayout(qt_layout) # Matplotlib Group mpl_group = QGroupBox(_("Graphics")) mpl_label = QLabel( _("Decide which backend to use to display graphics. " "If unsure, please select the <b>Automatic</b> " "backend.<br><br>" "<b>Note:</b> We support a very limited number " "of backends in our Python consoles. If you " "prefer to work with a different one, please use " "an IPython console.")) mpl_label.setWordWrap(True) backends = [("Automatic", 0), ("None", 1)] if not os.name == 'nt' and programs.is_module_installed('_tkinter'): backends.append(("Tkinter", 2)) backends = tuple(backends) mpl_backend_box = self.create_combobox( _("Backend:") + " ", backends, 'matplotlib/backend/value', tip=_("This option will be applied the " "next time a console is opened.")) mpl_installed = programs.is_module_installed('matplotlib') mpl_layout = QVBoxLayout() mpl_layout.addWidget(mpl_label) mpl_layout.addWidget(mpl_backend_box) mpl_group.setLayout(mpl_layout) mpl_group.setEnabled(mpl_installed) # ETS Group ets_group = QGroupBox(_("Enthought Tool Suite")) ets_label = QLabel( _("Enthought Tool Suite (ETS) supports " "PyQt4 (qt4) and wxPython (wx) graphical " "user interfaces.")) ets_label.setWordWrap(True) ets_edit = self.create_lineedit(_("ETS_TOOLKIT:"), 'ets_backend', alignment=Qt.Horizontal) ets_layout = QVBoxLayout() ets_layout.addWidget(ets_label) ets_layout.addWidget(ets_edit) ets_group.setLayout(ets_layout) if CONF.get('main_interpreter', 'default'): interpreter = get_python_executable() else: interpreter = CONF.get('main_interpreter', 'executable') ets_group.setEnabled( programs.is_module_installed("enthought.etsconfig.api", interpreter=interpreter)) tabs = QTabWidget() tabs.addTab(self.create_tab(interface_group, display_group, bg_group), _("Display")) tabs.addTab(self.create_tab(monitor_group, source_group), _("Introspection")) tabs.addTab(self.create_tab(pystartup_group), _("Advanced settings")) tabs.addTab(self.create_tab(qt_group, mpl_group, ets_group), _("External modules")) vlayout = QVBoxLayout() vlayout.addWidget(tabs) self.setLayout(vlayout)
class LoadDataWidget(FormBaseWidget): update_main_window_title = Signal() update_global_state = Signal() computations_complete = Signal(object) update_preview_map_range = Signal(str) signal_new_run_loaded = Signal(bool) # True/False - success/failed signal_loading_new_run = Signal() # Emitted before new run is loaded signal_data_channel_changed = Signal(bool) def __init__(self, *, gpc, gui_vars): super().__init__() # Global processing classes self.gpc = gpc # Global GUI variables (used for control of GUI state) self.gui_vars = gui_vars self.ref_main_window = self.gui_vars["ref_main_window"] self.update_global_state.connect( self.ref_main_window.update_widget_state) self.initialize() def initialize(self): v_spacing = global_gui_parameters["vertical_spacing_in_tabs"] vbox = QVBoxLayout() self._setup_wd_group() vbox.addWidget(self.group_wd) vbox.addSpacing(v_spacing) self._setup_load_group() vbox.addWidget(self.group_file) vbox.addSpacing(v_spacing) self._setup_sel_channel_group() vbox.addWidget(self.group_sel_channel) vbox.addSpacing(v_spacing) self._setup_spec_settings_group() vbox.addWidget(self.group_spec_settings) vbox.addSpacing(v_spacing) self._setup_preview_group() vbox.addWidget(self.group_preview) vbox.addStretch(1) self.setLayout(vbox) self._set_tooltips() def _setup_wd_group(self): self.group_wd = QGroupBox("Working Directory") self.pb_set_wd = PushButtonMinimumWidth("..") self.pb_set_wd.clicked.connect(self.pb_set_wd_clicked) self.le_wd = LineEditReadOnly() # Initial working directory. Set to the HOME directory for now current_dir = os.path.expanduser( self.gpc.get_current_working_directory()) self.le_wd.setText(current_dir) hbox = QHBoxLayout() hbox.addWidget(self.pb_set_wd) hbox.addWidget(self.le_wd) self.group_wd.setLayout(hbox) def _setup_load_group(self): self.group_file = QGroupBox("Load Data") self.pb_file = QPushButton("Read File ...") self.pb_file.clicked.connect(self.pb_file_clicked) self.pb_dbase = QPushButton("Load Run ...") self.pb_dbase.setEnabled( self.gui_vars["gui_state"]["databroker_available"]) self.pb_dbase.clicked.connect(self.pb_dbase_clicked) self.cb_file_all_channels = QCheckBox("All channels") self.cb_file_all_channels.setChecked(self.gpc.get_load_each_channel()) self.cb_file_all_channels.toggled.connect( self.cb_file_all_channels_toggled) self.le_file_default = "No data is loaded" self.le_file = LineEditReadOnly(self.le_file_default) self.pb_view_metadata = QPushButton("View Metadata ...") self.pb_view_metadata.setEnabled(False) self.pb_view_metadata.clicked.connect(self.pb_view_metadata_clicked) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(self.pb_file) hbox.addWidget(self.pb_dbase) hbox.addWidget(self.cb_file_all_channels) vbox.addLayout(hbox) vbox.addWidget(self.le_file) hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(self.pb_view_metadata) vbox.addLayout(hbox) self.group_file.setLayout(vbox) def _setup_sel_channel_group(self): self.group_sel_channel = QGroupBox("Select Channel For Processing") self.cbox_channel = QComboBox() self.cbox_channel.currentIndexChanged.connect( self.cbox_channel_index_changed) self._set_cbox_channel_items(items=[]) hbox = QHBoxLayout() hbox.addWidget(self.cbox_channel) self.group_sel_channel.setLayout(hbox) def _setup_spec_settings_group(self): self.group_spec_settings = QGroupBox("Total Spectrum Settings") self.pb_apply_mask = QPushButton("Apply Mask ...") self.pb_apply_mask.clicked.connect(self.pb_apply_mask_clicked) hbox = QHBoxLayout() hbox.addWidget(self.pb_apply_mask) hbox.addStretch(1) self.group_spec_settings.setLayout(hbox) def _setup_preview_group(self): self.group_preview = QGroupBox("Preview") self.list_preview = QListWidget() self.list_preview.itemChanged.connect(self.list_preview_item_changed) self.list_preview.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.list_preview.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._set_list_preview_items(items=[]) hbox = QHBoxLayout() hbox.addWidget(self.list_preview) self.group_preview.setLayout(hbox) def _set_tooltips(self): set_tooltip( self.pb_set_wd, "Select <b>Working Directory</b>. The Working Directory is " "used as <b>default</b> for loading and saving data and " "configuration files.", ) set_tooltip(self.le_wd, "Currently selected <b>Working Directory</b>") set_tooltip(self.pb_file, "Load data from a <b>file on disk</b>.") set_tooltip(self.pb_dbase, "Load data from a <b>database</b> (Databroker).") set_tooltip( self.cb_file_all_channels, "Load <b>all</b> available data channels (checked) or only the <b>sum</b> of the channels", ) set_tooltip( self.le_file, "The <b>name</b> of the loaded file or <b>ID</b> of the loaded run." ) set_tooltip(self.pb_view_metadata, "View scan <b>metadata</b> (if available)") set_tooltip( self.cbox_channel, "Select channel for processing. Typically the <b>sum</b> channel is used." ) set_tooltip( self.pb_apply_mask, "Load the mask from file and/or select spatial ROI. The mask and ROI " "are used in run <b>Preview</b> and fitting of the <b>total spectrum</b>.", ) set_tooltip( self.list_preview, "Data for the selected channels is displayed in <b>Preview</b> tab. " "The displayed <b>total spectrum</b> is computed for the selected " "ROI and using the loaded mask. If no mask or ROI are enabled, then " "total spectrum is computed over all pixels of the image.", ) def update_widget_state(self, condition=None): if condition == "tooltips": self._set_tooltips() state = self.gui_vars["gui_state"]["state_file_loaded"] self.group_sel_channel.setEnabled(state) self.group_spec_settings.setEnabled(state) self.group_preview.setEnabled(state) def _set_cbox_channel_items(self, *, items=None): """ Set items of the combo box for item selection. If the list of items is not specified, then it is loaded from the respective data structure. Parameters ---------- items: list(str) The list of items. The list may be cleared by calling `self._set_cbox_channel_items(items=[])` """ self.cbox_channel.currentIndexChanged.disconnect( self.cbox_channel_index_changed) self.cbox_channel.clear() if items is None: items = list(self.gpc.get_file_channel_list()) self.cbox_channel.addItems(items) self.cbox_channel.currentIndexChanged.connect( self.cbox_channel_index_changed) if len(items): # Select the first item (if there is at least one item) self.cbox_channel.setCurrentIndex(0) def _set_list_preview_items(self, *, items=None): """ Set items of the list for selecting channels in preview tab. If the list of items is not specified, then it is loaded from the respective data structure. Parameters ---------- items: list(str) The list of items. The list may be cleared by calling `self._set_cbox_channel_items(items=[])` """ self.list_preview.itemChanged.disconnect( self.list_preview_item_changed) self.list_preview.clear() if items is None: items = list(self.gpc.get_file_channel_list()) for s in items: wi = QListWidgetItem(s, self.list_preview) wi.setFlags(wi.flags() | Qt.ItemIsUserCheckable) wi.setFlags(wi.flags() & ~Qt.ItemIsSelectable) wi.setCheckState(Qt.Unchecked) # Adjust height so that it fits all the elements adjust_qlistwidget_height(self.list_preview, other_widgets=[self.group_preview, self]) # This will cause the preview data to be plotted (the plot is expected to be hidden, # since no channels were selected). Here we select the first channel in the list. for n, item in enumerate(items): state = Qt.Checked if self.gpc.is_dset_item_selected_for_preview( item) else Qt.Unchecked self.list_preview.item(n).setCheckState(state) self.list_preview.itemChanged.connect(self.list_preview_item_changed) def pb_set_wd_clicked(self): dir_current = self.le_wd.text() dir = QFileDialog.getExistingDirectory( self, "Select Working Directory", dir_current, QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks, ) if dir: self.gpc.set_current_working_directory(dir) self.le_wd.setText(dir) def pb_file_clicked(self): dir_current = self.gpc.get_current_working_directory() file_paths = QFileDialog.getOpenFileName(self, "Open Data File", dir_current, "HDF5 (*.h5);; All (*)") file_path = file_paths[0] if file_path: self.signal_loading_new_run.emit() def cb(file_path): result_dict = {} try: msg = self.gpc.open_data_file(file_path) status = True except Exception as ex: msg = str(ex) status = False result_dict.update({ "status": status, "msg": msg, "file_path": file_path }) return result_dict self._compute_in_background(cb, self.slot_file_clicked, file_path=file_path) @Slot(object) def slot_file_clicked(self, result): self._recover_after_compute(self.slot_file_clicked) status = result["status"] msg = result["msg"] # Message is empty if file loading failed file_path = result["file_path"] if status: file_text = f"'{self.gpc.get_loaded_file_name()}'" if self.gpc.is_scan_metadata_available(): file_text += f": ID#{self.gpc.get_metadata_scan_id()}" self.le_file.setText(file_text) self.gui_vars["gui_state"]["state_file_loaded"] = True # Invalidate fit. Fit must be rerun for new data. self.gui_vars["gui_state"]["state_model_fit_exists"] = False # Check if any datasets were loaded. self.gui_vars["gui_state"][ "state_xrf_map_exists"] = self.gpc.is_xrf_maps_available() # Disable the button for changing working directory. This is consistent # with the behavior of the old PyXRF, but will be changed in the future. self.pb_set_wd.setEnabled(False) # Enable/disable 'View Metadata' button self.pb_view_metadata.setEnabled( self.gpc.is_scan_metadata_available()) self.le_wd.setText(self.gpc.get_current_working_directory()) self.update_main_window_title.emit() self.update_global_state.emit() self._set_cbox_channel_items() self._set_list_preview_items() # Here we want to reset the range in the Total Count Map preview self.update_preview_map_range.emit("reset") self.signal_new_run_loaded.emit( True) # Data is loaded successfully if msg: # Display warning message if it was generated msgbox = QMessageBox(QMessageBox.Warning, "Warning", msg, QMessageBox.Ok, parent=self) msgbox.exec() else: self.le_file.setText(self.le_file_default) # Disable 'View Metadata' button self.pb_view_metadata.setEnabled(False) self.le_wd.setText(self.gpc.get_current_working_directory()) # Clear flags: the state now is "No data is loaded". clear_gui_state(self.gui_vars) self.update_global_state.emit() self.update_main_window_title.emit() self.update_global_state.emit() self._set_cbox_channel_items(items=[]) self._set_list_preview_items(items=[]) # Here we want to clear the range in the Total Count Map preview self.update_preview_map_range.emit("clear") self.signal_new_run_loaded.emit(False) # Failed to load data msg_str = (f"Incorrect format of input file '{file_path}': " f"PyXRF accepts only custom HDF (.h5) files." f"\n\nError message: {msg}") msgbox = QMessageBox(QMessageBox.Critical, "Error", msg_str, QMessageBox.Ok, parent=self) msgbox.exec() def pb_dbase_clicked(self): dlg = DialogSelectScan() if dlg.exec() == QDialog.Accepted: mode, id_uid = dlg.get_id_uid() self.signal_loading_new_run.emit() def cb(id_uid): result_dict = {} try: msg, file_name = self.gpc.load_run_from_db(id_uid) status = True except Exception as ex: msg, file_name = str(ex), "" status = False result_dict.update({ "status": status, "msg": msg, "id_uid": id_uid, "file_name": file_name }) return result_dict self._compute_in_background(cb, self.slot_dbase_clicked, id_uid=id_uid) @Slot(object) def slot_dbase_clicked(self, result): self._recover_after_compute(self.slot_dbase_clicked) status = result["status"] msg = result["msg"] # Message is empty if file loading failed id_uid = result["id_uid"] # file_name = result["file_name"] if status: file_text = f"'{self.gpc.get_loaded_file_name()}'" if self.gpc.is_scan_metadata_available(): file_text += f": ID#{self.gpc.get_metadata_scan_id()}" self.le_file.setText(file_text) self.gui_vars["gui_state"]["state_file_loaded"] = True # Invalidate fit. Fit must be rerun for new data. self.gui_vars["gui_state"]["state_model_fit_exists"] = False # Check if any datasets were loaded. self.gui_vars["gui_state"][ "state_xrf_map_exists"] = self.gpc.is_xrf_maps_available() # Disable the button for changing working directory. This is consistent # with the behavior of the old PyXRF, but will be changed in the future. self.pb_set_wd.setEnabled(False) # Enable/disable 'View Metadata' button self.pb_view_metadata.setEnabled( self.gpc.is_scan_metadata_available()) self.le_wd.setText(self.gpc.get_current_working_directory()) self.update_main_window_title.emit() self.update_global_state.emit() self._set_cbox_channel_items() self._set_list_preview_items() # Here we want to reset the range in the Total Count Map preview self.update_preview_map_range.emit("reset") self.signal_new_run_loaded.emit( True) # Data is loaded successfully if msg: # Display warning message if it was generated msgbox = QMessageBox(QMessageBox.Warning, "Warning", msg, QMessageBox.Ok, parent=self) msgbox.exec() else: self.le_file.setText(self.le_file_default) # Disable 'View Metadata' button self.pb_view_metadata.setEnabled(False) self.le_wd.setText(self.gpc.get_current_working_directory()) # Clear flags: the state now is "No data is loaded". clear_gui_state(self.gui_vars) self.update_global_state.emit() self.update_main_window_title.emit() self.update_global_state.emit() self._set_cbox_channel_items(items=[]) self._set_list_preview_items(items=[]) # Here we want to clear the range in the Total Count Map preview self.update_preview_map_range.emit("clear") self.signal_new_run_loaded.emit(False) # Failed to load data msg_str = f"Failed to load scan '{id_uid}'.\n\nError message: {msg}" msgbox = QMessageBox(QMessageBox.Critical, "Error", msg_str, QMessageBox.Ok, parent=self) msgbox.exec() def pb_apply_mask_clicked(self): map_size = self.gpc.get_dataset_map_size() map_size = map_size if (map_size is not None) else (0, 0) dlg = DialogLoadMask() dlg.set_image_size(n_rows=map_size[0], n_columns=map_size[1]) roi = self.gpc.get_preview_spatial_roi() dlg.set_roi( row_start=roi["row_start"], column_start=roi["col_start"], row_end=roi["row_end"], column_end=roi["col_end"], ) dlg.set_roi_active(self.gpc.is_roi_selection_active()) dlg.set_default_directory(self.gpc.get_current_working_directory()) dlg.set_mask_file_path(self.gpc.get_mask_selection_file_path()) dlg.set_mask_file_active(self.gpc.is_mask_selection_active()) if dlg.exec() == QDialog.Accepted: roi_keys = ("row_start", "col_start", "row_end", "col_end") roi_list = dlg.get_roi() roi_selected = {k: roi_list[n] for n, k in enumerate(roi_keys)} self.gpc.set_preview_spatial_roi(roi_selected) self.gpc.set_roi_selection_active(dlg.get_roi_active()) self.gpc.set_mask_selection_file_path(dlg.get_mask_file_path()) self.gpc.set_mask_selection_active(dlg.get_mask_file_active()) def cb(): try: # TODO: proper error processing is needed here (exception RuntimeError) self.gpc.apply_mask_to_datasets() success = True msg = "" except Exception as ex: success = False msg = str(ex) return {"success": success, "msg": msg} self._compute_in_background(cb, self.slot_apply_mask_clicked) @Slot(object) def slot_apply_mask_clicked(self, result): if not result["success"]: msg = f"Error occurred while applying the ROI selection:\nException: {result['msg']}" logger.error(f"{msg}") mb_error = QMessageBox(QMessageBox.Critical, "Error", f"{msg}", QMessageBox.Ok, parent=self) mb_error.exec() # Here we want to expand the range in the Total Count Map preview if needed self.update_preview_map_range.emit("update") self._recover_after_compute(self.slot_apply_mask_clicked) def pb_view_metadata_clicked(self): dlg = DialogViewMetadata() metadata_string = self.gpc.get_formatted_metadata() dlg.setText(metadata_string) dlg.exec() def cb_file_all_channels_toggled(self, state): self.gpc.set_load_each_channel(state) def cbox_channel_index_changed(self, index): def cb(index): try: self.gpc.set_data_channel(index) success, msg = True, "" except Exception as ex: success = False msg = str(ex) return {"success": success, "msg": msg} self._compute_in_background(cb, self.slot_channel_index_changed, index=index) @Slot(object) def slot_channel_index_changed(self, result): self._recover_after_compute(self.slot_channel_index_changed) if result["success"]: self.signal_data_channel_changed.emit(True) else: self.signal_data_channel_changed.emit(False) msg = result["msg"] msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() def list_preview_item_changed(self, list_item): # Find the index of the list item that was checked/unchecked ind = -1 for n in range(self.list_preview.count()): if self.list_preview.item(n) == list_item: ind = n # Get the state of the checkbox (checked -> 2, unchecked -> 0) state = list_item.checkState() # The name of the dataset dset_name = self.gpc.get_file_channel_list()[ind] def cb(): try: self.gpc.select_preview_dataset(dset_name=dset_name, is_visible=bool(state)) success, msg = True, "" except Exception as ex: success = False msg = str(ex) return {"success": success, "msg": msg} self._compute_in_background(cb, self.slot_preview_items_changed) @Slot(object) def slot_preview_items_changed(self, result): if not result["success"]: # The error shouldn't actually happen here. This is to prevent potential crashes. msg = f"Error occurred: {result['msg']}.\nData may need to be reloaded to continue processing." msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() # Here we want to expand the range in the Total Count Map preview if needed self.update_preview_map_range.emit("expand") self._recover_after_compute(self.slot_preview_items_changed) def _compute_in_background(self, func, slot, *args, **kwargs): """ Run function `func` in a background thread. Send the signal `self.computations_complete` once computation is finished. Parameters ---------- func: function Reference to a function that is supposed to be executed at the background. The function return value is passed as a signal parameter once computation is complete. slot: qtpy.QtCore.Slot or None Reference to a slot. If not None, then the signal `self.computation_complete` is connected to this slot. args, kwargs arguments of the function `func`. """ signal_complete = self.computations_complete def func_to_run(func, *args, **kwargs): class LoadFile(QRunnable): def run(self): result_dict = func(*args, **kwargs) signal_complete.emit(result_dict) return LoadFile() if slot is not None: self.computations_complete.connect(slot) self.gui_vars["gui_state"]["running_computations"] = True self.update_global_state.emit() QThreadPool.globalInstance().start(func_to_run(func, *args, **kwargs)) def _recover_after_compute(self, slot): """ The function should be called after the signal `self.computations_complete` is received. The slot should be the same as the one used when calling `self.compute_in_background`. """ if slot is not None: self.computations_complete.disconnect(slot) self.gui_vars["gui_state"]["running_computations"] = False self.update_global_state.emit()
def setup_page(self): newcb = self.create_checkbox # Interface Group interface_group = QGroupBox(_("Interface")) banner_box = newcb(_("Display initial banner"), 'show_banner', tip=_("This option lets you hide the message " "shown at\nthe top of the console when " "it's opened.")) pager_box = newcb(_("Use a pager to display additional text inside " "the console"), 'use_pager', tip=_("Useful if you don't want to fill the " "console with long help or completion " "texts.\n" "Note: Use the Q key to get out of the " "pager.")) calltips_box = newcb(_("Show calltips"), 'show_calltips') ask_box = newcb(_("Ask for confirmation before closing"), 'ask_before_closing') reset_namespace_box = newcb( _("Ask for confirmation before removing all user-defined " "variables"), 'show_reset_namespace_warning', tip=_("This option lets you hide the warning message shown\n" "when resetting the namespace from Spyder.")) show_time_box = newcb(_("Show elapsed time"), 'show_elapsed_time') ask_restart_box = newcb( _("Ask for confirmation before restarting"), 'ask_before_restart', tip=_("This option lets you hide the warning message shown\n" "when restarting the kernel.")) interface_layout = QVBoxLayout() interface_layout.addWidget(banner_box) interface_layout.addWidget(pager_box) interface_layout.addWidget(calltips_box) interface_layout.addWidget(ask_box) interface_layout.addWidget(reset_namespace_box) interface_layout.addWidget(show_time_box) interface_layout.addWidget(ask_restart_box) interface_group.setLayout(interface_layout) comp_group = QGroupBox(_("Completion Type")) comp_label = QLabel(_("Decide what type of completion to use")) comp_label.setWordWrap(True) completers = [(_("Graphical"), 0), (_("Terminal"), 1), (_("Plain"), 2)] comp_box = self.create_combobox( _("Completion:") + " ", completers, 'completion_type') comp_layout = QVBoxLayout() comp_layout.addWidget(comp_label) comp_layout.addWidget(comp_box) comp_group.setLayout(comp_layout) # Source Code Group source_code_group = QGroupBox(_("Source code")) buffer_spin = self.create_spinbox( _("Buffer: "), _(" lines"), 'buffer_size', min_=-1, max_=1000000, step=100, tip=_("Set the maximum number of lines of text shown in the\n" "console before truncation. Specifying -1 disables it\n" "(not recommended!)")) source_code_layout = QVBoxLayout() source_code_layout.addWidget(buffer_spin) source_code_group.setLayout(source_code_layout) # --- Graphics --- # Pylab Group pylab_group = QGroupBox(_("Support for graphics (Matplotlib)")) pylab_box = newcb(_("Activate support"), 'pylab') autoload_pylab_box = newcb( _("Automatically load Pylab and NumPy modules"), 'pylab/autoload', tip=_("This lets you load graphics support without importing\n" "the commands to do plots. Useful to work with other\n" "plotting libraries different to Matplotlib or to develop\n" "GUIs with Spyder.")) autoload_pylab_box.setEnabled(self.get_option('pylab')) pylab_box.toggled.connect(autoload_pylab_box.setEnabled) pylab_layout = QVBoxLayout() pylab_layout.addWidget(pylab_box) pylab_layout.addWidget(autoload_pylab_box) pylab_group.setLayout(pylab_layout) # Pylab backend Group inline = _("Inline") automatic = _("Automatic") backend_group = QGroupBox(_("Graphics backend")) bend_label = QLabel( _("Decide how graphics are going to be displayed " "in the console. If unsure, please select " "<b>%s</b> to put graphics inside the " "console or <b>%s</b> to interact with " "them (through zooming and panning) in a " "separate window.") % (inline, automatic)) bend_label.setWordWrap(True) backends = [(inline, 0), (automatic, 1), ("Qt5", 2), ("Qt4", 3)] if sys.platform == 'darwin': backends.append(("OS X", 4)) if sys.platform.startswith('linux'): backends.append(("Gtk3", 5)) backends.append(("Gtk", 6)) if PY2: backends.append(("Wx", 7)) backends.append(("Tkinter", 8)) backends = tuple(backends) backend_box = self.create_combobox( _("Backend:") + " ", backends, 'pylab/backend', default=0, tip=_("This option will be applied the next time a console is " "opened.")) backend_layout = QVBoxLayout() backend_layout.addWidget(bend_label) backend_layout.addWidget(backend_box) backend_group.setLayout(backend_layout) backend_group.setEnabled(self.get_option('pylab')) pylab_box.toggled.connect(backend_group.setEnabled) # Inline backend Group inline_group = QGroupBox(_("Inline backend")) inline_label = QLabel( _("Decide how to render the figures created by " "this backend")) inline_label.setWordWrap(True) formats = (("PNG", 0), ("SVG", 1)) format_box = self.create_combobox(_("Format:") + " ", formats, 'pylab/inline/figure_format', default=0) resolution_spin = self.create_spinbox( _("Resolution:") + " ", " " + _("dpi"), 'pylab/inline/resolution', min_=50, max_=999, step=0.1, tip=_("Only used when the format is PNG. Default is " "72")) width_spin = self.create_spinbox(_("Width:") + " ", " " + _("inches"), 'pylab/inline/width', min_=2, max_=20, step=1, tip=_("Default is 6")) height_spin = self.create_spinbox(_("Height:") + " ", " " + _("inches"), 'pylab/inline/height', min_=1, max_=20, step=1, tip=_("Default is 4")) bbox_inches_box = newcb(_("Use a tight layout for inline plots"), 'pylab/inline/bbox_inches', tip=_( "Sets bbox_inches to \"tight\" when\n" "plotting inline with matplotlib.\n" "When enabled, can cause discrepancies\n" "between the image displayed inline and\n" "that created using savefig.")) inline_v_layout = QVBoxLayout() inline_v_layout.addWidget(inline_label) inline_layout = QGridLayout() inline_layout.addWidget(format_box.label, 1, 0) inline_layout.addWidget(format_box.combobox, 1, 1) inline_layout.addWidget(resolution_spin.plabel, 2, 0) inline_layout.addWidget(resolution_spin.spinbox, 2, 1) inline_layout.addWidget(resolution_spin.slabel, 2, 2) inline_layout.addWidget(width_spin.plabel, 3, 0) inline_layout.addWidget(width_spin.spinbox, 3, 1) inline_layout.addWidget(width_spin.slabel, 3, 2) inline_layout.addWidget(height_spin.plabel, 4, 0) inline_layout.addWidget(height_spin.spinbox, 4, 1) inline_layout.addWidget(height_spin.slabel, 4, 2) inline_layout.addWidget(bbox_inches_box, 5, 0, 1, 4) inline_h_layout = QHBoxLayout() inline_h_layout.addLayout(inline_layout) inline_h_layout.addStretch(1) inline_v_layout.addLayout(inline_h_layout) inline_group.setLayout(inline_v_layout) inline_group.setEnabled(self.get_option('pylab')) pylab_box.toggled.connect(inline_group.setEnabled) # --- Startup --- # Run lines Group run_lines_group = QGroupBox(_("Run code")) run_lines_label = QLabel( _("You can run several lines of code when " "a console is started. Please introduce " "each one separated by semicolons and a " "space, for example:<br>" "<i>import os; import sys</i>")) run_lines_label.setWordWrap(True) run_lines_edit = self.create_lineedit(_("Lines:"), 'startup/run_lines', '', alignment=Qt.Horizontal) run_lines_layout = QVBoxLayout() run_lines_layout.addWidget(run_lines_label) run_lines_layout.addWidget(run_lines_edit) run_lines_group.setLayout(run_lines_layout) # Run file Group run_file_group = QGroupBox(_("Run a file")) run_file_label = QLabel( _("You can also run a whole file at startup " "instead of just some lines (This is " "similar to have a PYTHONSTARTUP file).")) run_file_label.setWordWrap(True) file_radio = newcb(_("Use the following file:"), 'startup/use_run_file', False) run_file_browser = self.create_browsefile('', 'startup/run_file', '') run_file_browser.setEnabled(False) file_radio.toggled.connect(run_file_browser.setEnabled) run_file_layout = QVBoxLayout() run_file_layout.addWidget(run_file_label) run_file_layout.addWidget(file_radio) run_file_layout.addWidget(run_file_browser) run_file_group.setLayout(run_file_layout) # ---- Advanced settings ---- # Enable Jedi completion jedi_group = QGroupBox(_("Jedi completion")) jedi_label = QLabel( _("Enable Jedi-based <tt>Tab</tt> completion " "in the IPython console; similar to the " "greedy completer, but without evaluating " "the code.<br>" "<b>Warning:</b> Slows down your console " "when working with large dataframes!")) jedi_label.setWordWrap(True) jedi_box = newcb(_("Use Jedi completion in the IPython console"), "jedi_completer", tip=_("<b>Warning</b>: " "Slows down your console when working with " "large dataframes!<br>" "Allows completion of nested lists etc.")) jedi_layout = QVBoxLayout() jedi_layout.addWidget(jedi_label) jedi_layout.addWidget(jedi_box) jedi_group.setLayout(jedi_layout) # Greedy completer group greedy_group = QGroupBox(_("Greedy completion")) greedy_label = QLabel( _("Enable <tt>Tab</tt> completion on elements " "of lists, results of function calls, etc, " "<i>without</i> assigning them to a variable, " "like <tt>li[0].<Tab></tt> or " "<tt>ins.meth().<Tab></tt> <br>" "<b>Warning:</b> Due to a bug, IPython's " "greedy completer requires a leading " "<tt><Space></tt> for some completions; " "e.g. <tt>np.sin(<Space>np.<Tab>" "</tt> works while <tt>np.sin(np.<Tab> " "</tt> doesn't.")) greedy_label.setWordWrap(True) greedy_box = newcb(_("Use greedy completion in the IPython console"), "greedy_completer", tip="<b>Warning</b>: It can be unsafe because the " "code is actually evaluated when you press " "<tt>Tab</tt>.") greedy_layout = QVBoxLayout() greedy_layout.addWidget(greedy_label) greedy_layout.addWidget(greedy_box) greedy_group.setLayout(greedy_layout) # Autocall group autocall_group = QGroupBox(_("Autocall")) autocall_label = QLabel( _("Autocall makes IPython automatically call " "any callable object even if you didn't " "type explicit parentheses.<br>" "For example, if you type <i>str 43</i> it " "becomes <i>str(43)</i> automatically.")) autocall_label.setWordWrap(True) smart = _('Smart') full = _('Full') autocall_opts = ((_('Off'), 0), (smart, 1), (full, 2)) autocall_box = self.create_combobox( _("Autocall: "), autocall_opts, 'autocall', default=0, tip=_("On <b>%s</b> mode, Autocall is not applied if " "there are no arguments after the callable. On " "<b>%s</b> mode, all callable objects are " "automatically called (even if no arguments are " "present).") % (smart, full)) autocall_layout = QVBoxLayout() autocall_layout.addWidget(autocall_label) autocall_layout.addWidget(autocall_box) autocall_group.setLayout(autocall_layout) # Sympy group sympy_group = QGroupBox(_("Symbolic Mathematics")) sympy_label = QLabel( _("Perfom symbolic operations in the console " "(e.g. integrals, derivatives, vector " "calculus, etc) and get the outputs in a " "beautifully printed style (it requires the " "Sympy module).")) sympy_label.setWordWrap(True) sympy_box = newcb(_("Use symbolic math"), "symbolic_math", tip=_("This option loads the Sympy library to work " "with.<br>Please refer to its documentation " "to learn how to use it.")) sympy_layout = QVBoxLayout() sympy_layout.addWidget(sympy_label) sympy_layout.addWidget(sympy_box) sympy_group.setLayout(sympy_layout) # Prompts group prompts_group = QGroupBox(_("Prompts")) prompts_label = QLabel( _("Modify how Input and Output prompts are " "shown in the console.")) prompts_label.setWordWrap(True) in_prompt_edit = self.create_lineedit( _("Input prompt:"), 'in_prompt', '', _('Default is<br>' 'In [<span class="in-prompt-number">' '%i</span>]:'), alignment=Qt.Horizontal) out_prompt_edit = self.create_lineedit( _("Output prompt:"), 'out_prompt', '', _('Default is<br>' 'Out[<span class="out-prompt-number">' '%i</span>]:'), alignment=Qt.Horizontal) prompts_layout = QVBoxLayout() prompts_layout.addWidget(prompts_label) prompts_g_layout = QGridLayout() prompts_g_layout.addWidget(in_prompt_edit.label, 0, 0) prompts_g_layout.addWidget(in_prompt_edit.textbox, 0, 1) prompts_g_layout.addWidget(out_prompt_edit.label, 1, 0) prompts_g_layout.addWidget(out_prompt_edit.textbox, 1, 1) prompts_layout.addLayout(prompts_g_layout) prompts_group.setLayout(prompts_layout) # Windows adjustments windows_group = QGroupBox(_("Windows adjustments")) hide_cmd_windows = newcb( _("Hide command line output windows " "generated by the subprocess module."), 'hide_cmd_windows') windows_layout = QVBoxLayout() windows_layout.addWidget(hide_cmd_windows) windows_group.setLayout(windows_layout) # --- Tabs organization --- tabs = QTabWidget() tabs.addTab( self.create_tab(interface_group, comp_group, source_code_group), _("Display")) tabs.addTab(self.create_tab(pylab_group, backend_group, inline_group), _("Graphics")) tabs.addTab(self.create_tab(run_lines_group, run_file_group), _("Startup")) tabs.addTab( self.create_tab(jedi_group, greedy_group, autocall_group, sympy_group, prompts_group, windows_group), _("Advanced Settings")) vlayout = QVBoxLayout() vlayout.addWidget(tabs) self.setLayout(vlayout)
class ModelWidget(FormBaseWidget): # Signal that is sent (to main window) to update global state of the program update_global_state = Signal() computations_complete = Signal(object) # Signal is emitted when a new model is loaded (or computed). # True - model loaded successfully, False - otherwise # In particular, the signal may be used to update the widgets that depend on incident energy, # because it may change as the model is loaded. signal_model_loaded = Signal(bool) # Incident energy or selected range changed (plots need to be redrawn) signal_incident_energy_or_range_changed = Signal() # Sent after the completion of total spectrum fitting signal_total_spectrum_fitting_completed = Signal(bool) def __init__(self, *, gpc, gui_vars): super().__init__() self._fit_available = False # Currently selected emission line. self._selected_eline = "" # Global processing classes self.gpc = gpc # Global GUI variables (used for control of GUI state) self.gui_vars = gui_vars # Reference to the main window. The main window will hold # references to all non-modal windows that could be opened # from multiple places in the program. self.ref_main_window = self.gui_vars["ref_main_window"] self.update_global_state.connect( self.ref_main_window.update_widget_state) self.initialize() def initialize(self): v_spacing = global_gui_parameters["vertical_spacing_in_tabs"] vbox = QVBoxLayout() self._setup_model_params_group() vbox.addWidget(self.group_model_params) vbox.addSpacing(v_spacing) self._setup_add_remove_elines_button() vbox.addWidget(self.pb_manage_emission_lines) vbox.addSpacing(v_spacing) self._setup_settings_group() vbox.addWidget(self.group_settings) vbox.addSpacing(v_spacing) self._setup_model_fitting_group() vbox.addWidget(self.group_model_fitting) self.setLayout(vbox) self._set_tooltips() # Timer is currently used to simulate processing self._timer = None self._timer_counter = 0 def _setup_model_params_group(self): self.group_model_params = QGroupBox("Load/Save Model Parameters") self.pb_find_elines = QPushButton("Find Automatically ...") self.pb_find_elines.clicked.connect(self.pb_find_elines_clicked) self.pb_load_elines = QPushButton("Load From File ...") self.pb_load_elines.clicked.connect(self.pb_load_elines_clicked) self.pb_load_qstandard = QPushButton("Load Quantitative Standard ...") self.pb_load_qstandard.clicked.connect(self.pb_load_qstandard_clicked) self.pb_save_elines = QPushButton("Save Parameters to File ...") self.pb_save_elines.clicked.connect(self.pb_save_elines_clicked) # This field will display the name of he last loaded parameter file, # Serial/Name of the quantitative standard, or 'no parameters' message self.le_param_fln = LineEditReadOnly("No parameter file is loaded") vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(self.pb_find_elines) hbox.addWidget(self.pb_load_elines) vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(self.pb_save_elines) vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(self.pb_load_qstandard) vbox.addLayout(hbox) vbox.addWidget(self.le_param_fln) self.group_model_params.setLayout(vbox) def _setup_add_remove_elines_button(self): self.pb_manage_emission_lines = QPushButton( "Add/Remove Emission Lines ...") self.pb_manage_emission_lines.clicked.connect( self.pb_manage_emission_lines_clicked) def _setup_settings_group(self): self.group_settings = QGroupBox("Settings for Fitting Algorithm") self.pb_fit_param_general = QPushButton("General ...") self.pb_fit_param_general.clicked.connect( self.pb_fit_param_general_clicked) self.pb_fit_param_shared = QPushButton("Shared ...") self.pb_fit_param_shared.clicked.connect( self.pb_fit_param_shared_clicked) self.pb_fit_param_lines = QPushButton("Lines ...") self.pb_fit_param_lines.clicked.connect( self.pb_fit_param_lines_clicked) fit_strategy_list = self.gpc.get_fit_strategy_list() combo_items = [fitting_preset_names[_] for _ in fit_strategy_list] combo_items = ["None"] + combo_items self.cb_step1 = QComboBox() self.cb_step1.setMinimumWidth(150) self.cb_step1.addItems(combo_items) self.cb_step1.setCurrentIndex(1) # Should also be set based on data self.cb_step2 = QComboBox() self.cb_step2.setMinimumWidth(150) self.cb_step2.addItems(combo_items) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(self.pb_fit_param_general) hbox.addWidget(self.pb_fit_param_shared) hbox.addWidget(self.pb_fit_param_lines) vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(QLabel("Fitting step 1:")) hbox.addSpacing(20) hbox.addWidget(self.cb_step1) hbox.addStretch(1) vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(QLabel("Fitting step 2:")) hbox.addSpacing(20) hbox.addWidget(self.cb_step2) hbox.addStretch(1) vbox.addLayout(hbox) self.group_settings.setLayout(vbox) def _setup_model_fitting_group(self): self.group_model_fitting = QGroupBox( "Model Fitting Based on Total Spectrum") self.pb_start_fitting = QPushButton("Start Fitting") self.pb_start_fitting.clicked.connect(self.pb_start_fitting_clicked) self.pb_save_spectrum = QPushButton("Save Spectrum/Fit ...") self.pb_save_spectrum.clicked.connect(self.pb_save_spectrum_clicked) self.le_fitting_results = LineEditReadOnly() vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(self.pb_start_fitting) hbox.addWidget(self.pb_save_spectrum) vbox.addLayout(hbox) vbox.addWidget(self.le_fitting_results) self.group_model_fitting.setLayout(vbox) def _set_tooltips(self): set_tooltip( self.pb_find_elines, "Automatically find emission lines from <b>total spectrum</b>.") set_tooltip( self.pb_load_elines, "Load model parameters, including selected emission lines from <b>JSON</b> file, " "which was previously save using <b>Save Parameters to File ...</b>.", ) set_tooltip( self.pb_load_qstandard, "Load <b>quantitative standard</b>. The model is reset and the emission lines " "that fit within the selected range of energies are added to the list " "of emission lines.", ) set_tooltip( self.pb_save_elines, "Save the model parameters including the parameters of the selected emission lines " "to <b>JSON</b> file.", ) set_tooltip( self.le_param_fln, "The name of the recently loaded <b>parameter file</b> or serial number " "and name of the loaded <b>quantitative standard</b>", ) set_tooltip( self.pb_manage_emission_lines, "Open a user friendly interface that allows to <b>add and remove emission lines</b> " "to the list or <b>modify parameters</b> of the selected emission lines", ) set_tooltip(self.pb_fit_param_general, "<b>General settings</b> for fitting algorithms.") set_tooltip( self.pb_fit_param_shared, "Access to low-level control of the total spectrum fitting algorithm: parameters shared " "by models of all emission lines.", ) set_tooltip( self.pb_fit_param_lines, "Access to low-level control of the total spectrum fitting algorithm: adjust parameters " "for each emission line of the selected elements; modify preset fitting configurations.", ) set_tooltip( self.cb_step1, "Select preset fitting configuration for <b>Step 1</b>. " "Click <b>Elements...</b> and <b>Global Parameters...</b> " "buttons to open dialog boxes to configure the presets.", ) set_tooltip( self.cb_step2, "Select preset fitting configuration for <b>Step 2</b>. " "Click <b>Elements...</b> and <b>Global Parameters...</b> " "buttons to open dialog boxes to configure the presets.", ) set_tooltip( self.pb_start_fitting, "Click the button to <b>run fitting of total spectrum</b>. The result of fitting includes " "the refined set of emission line parameters. The fitted spectrum is displayed in " "<b>'Fitting Model'</b> tab and can be saved by clicking <b>'Save Spectrum/Fit ...'</b> button.", ) set_tooltip( self.pb_save_spectrum, "Save <b>raw and fitted total spectra</b>. Click <b>'Start Fitting'</b> to perform fitting " "before saving the spectrum", ) set_tooltip( self.le_fitting_results, "<b>Output parameters</b> produced by the fitting algorithm") def update_widget_state(self, condition=None): if condition == "tooltips": self._set_tooltips() state_file_loaded = self.gui_vars["gui_state"]["state_file_loaded"] state_model_exist = self.gui_vars["gui_state"]["state_model_exists"] # state_model_fit_exists = self.gui_vars["gui_state"]["state_model_fit_exists"] self.group_model_params.setEnabled(state_file_loaded) self.pb_save_elines.setEnabled(state_file_loaded & state_model_exist) self.pb_manage_emission_lines.setEnabled(state_file_loaded & state_model_exist) self.group_settings.setEnabled(state_file_loaded & state_model_exist) self.group_settings.setEnabled(state_file_loaded & state_model_exist) self.group_model_fitting.setEnabled(state_file_loaded & state_model_exist) # self.pb_save_spectrum.setEnabled(state_file_loaded & state_model_exist & state_model_fit_exists) self.pb_save_spectrum.setEnabled(state_file_loaded & state_model_exist) def pb_find_elines_clicked(self): dialog_data = self.gpc.get_autofind_elements_params() dlg = DialogFindElements() dlg.set_dialog_data(dialog_data) ret = dlg.exec() if ret: dialog_data = dlg.get_dialog_data() find_elements_requested = dlg.find_elements_requested update_model = self.gui_vars["gui_state"]["state_model_exists"] def cb(): range_changed = self.gpc.set_autofind_elements_params( dialog_data, update_model=update_model, update_fitting_params=not find_elements_requested) if find_elements_requested: self.gpc.find_elements_automatically() return { "range_changed": range_changed, "find_elements_requested": find_elements_requested } self._compute_in_background(cb, self.slot_find_elines_clicked) @Slot(object) def slot_find_elines_clicked(self, result): range_changed = result["range_changed"] find_elements_requested = result["find_elements_requested"] self._set_fit_status(False) self._recover_after_compute(self.slot_find_elines_clicked) if range_changed: self.signal_incident_energy_or_range_changed.emit() self.gpc.fitting_parameters_changed() if find_elements_requested: msg = "Emission lines were detected automatically" else: msg = "Parameters were upadated" self.le_param_fln.setText(msg) if find_elements_requested: self.gui_vars["gui_state"]["state_model_exists"] = True self.gui_vars["gui_state"]["state_model_fit_exists"] = False self.signal_model_loaded.emit(True) self.update_global_state.emit() logger.info("Automated element search is complete") @Slot(str) def slot_selection_item_changed(self, eline): self._selected_eline = eline def _get_load_elines_cb(self): def cb(file_name, incident_energy_from_param_file=None): try: completed, question = self.gpc.load_parameters_from_file( file_name, incident_energy_from_param_file) success = True change_state = True msg = "" except IOError as ex: completed, question = True, "" success = False change_state = False msg = str(ex) except Exception as ex: completed, question = True, "" success = False change_state = True msg = str(ex) result_dict = { "completed": completed, "question": question, "success": success, "change_state": change_state, "msg": msg, "file_name": file_name, } return result_dict return cb def pb_load_elines_clicked(self): current_dir = self.gpc.get_current_working_directory() file_name = QFileDialog.getOpenFileName( self, "Select File with Model Parameters", current_dir, "JSON (*.json);; All (*)") file_name = file_name[0] if file_name: cb = self._get_load_elines_cb() self._compute_in_background(cb, self.slot_load_elines_clicked, file_name=file_name, incident_energy_from_param_file=None) @Slot(object) def slot_load_elines_clicked(self, results): self._recover_after_compute(self.slot_load_elines_clicked) completed = results["completed"] file_name = results["file_name"] msg = results["msg"] if not completed: mb = QMessageBox( QMessageBox.Question, "Question", results["question"], QMessageBox.Yes | QMessageBox.No, parent=self, ) answer = mb.exec() == QMessageBox.Yes cb = self._get_load_elines_cb() self._compute_in_background(cb, self.slot_load_elines_clicked, file_name=file_name, incident_energy_from_param_file=answer) return if results["success"]: _, fln = os.path.split(file_name) msg = f"File: '{fln}'" self.le_param_fln.setText(msg) self._set_fit_status(False) self.gui_vars["gui_state"]["state_model_exists"] = True self.gui_vars["gui_state"]["state_model_fit_exists"] = False self.signal_model_loaded.emit(True) self.update_global_state.emit() else: if results["change_state"]: logger.error( f"Exception: error occurred while loading parameters: {msg}" ) mb_error = QMessageBox( QMessageBox.Critical, "Error", f"Error occurred while processing loaded parameters: {msg}", QMessageBox.Ok, parent=self, ) mb_error.exec() # Here the parameters were loaded and processing was partially performed, # so change the state of the program self.gui_vars["gui_state"]["state_model_exists"] = False self.gui_vars["gui_state"]["state_model_fit_exists"] = False self.signal_model_loaded.emit(False) self.update_global_state.emit() else: # It doesn't seem that the state of the program needs to be changed if # the file was not loaded at all logger.error(f"Exception: {msg}") mb_error = QMessageBox(QMessageBox.Critical, "Error", f"{msg}", QMessageBox.Ok, parent=self) mb_error.exec() def pb_load_qstandard_clicked(self): qe_param_built_in, qe_param_custom, qe_standard_selected = self.gpc.get_quant_standard_list( ) dlg = DialogSelectQuantStandard() dlg.set_standards(qe_param_built_in, qe_param_custom, qe_standard_selected) ret = dlg.exec() if ret: selected_standard = dlg.get_selected_standard() def cb(selected_standard): try: if selected_standard is None: raise RuntimeError( "The selected standard is not found.") self.gpc.set_selected_quant_standard(selected_standard) success, msg = True, "" except Exception as ex: success, msg = False, str(ex) return { "success": success, "msg": msg, "selected_standard": selected_standard } self._compute_in_background(cb, self.slot_load_qstandard_clicked, selected_standard=selected_standard) @Slot(object) def slot_load_qstandard_clicked(self, result): self._recover_after_compute(self.slot_load_qstandard_clicked) if result["success"]: selected_standard = result["selected_standard"] msg = f"QS: '{selected_standard['name']}'" if self.gpc.is_quant_standard_custom(selected_standard): msg += " (user-defined)" self.le_param_fln.setText(msg) self.gpc.process_peaks_from_quantitative_sample_data() self._set_fit_status(False) self.gui_vars["gui_state"]["state_model_exists"] = True self.gui_vars["gui_state"]["state_model_fit_exists"] = False self.signal_model_loaded.emit(True) self.update_global_state.emit() else: msg = result["msg"] msgbox = QMessageBox(QMessageBox.Critical, "Failed to Load Quantitative Standard", msg, QMessageBox.Ok, parent=self) msgbox.exec() def pb_save_elines_clicked(self): current_dir = self.gpc.get_current_working_directory() fln = os.path.join(current_dir, "model_parameters.json") file_name = QFileDialog.getSaveFileName( self, "Select File to Save Model Parameters", fln, "JSON (*.json);; All (*)") file_name = file_name[0] if file_name: try: self.gpc.save_param_to_file(file_name) logger.debug( f"Model parameters were saved to the file '{file_name}'") except Exception as ex: msg = str(ex) msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() else: logger.debug("Saving model parameters was skipped.") def pb_manage_emission_lines_clicked(self): # Position the window in relation ot the main window (only when called once) pos = self.ref_main_window.pos() self.ref_main_window.wnd_manage_emission_lines.position_once( pos.x(), pos.y()) if not self.ref_main_window.wnd_manage_emission_lines.isVisible(): self.ref_main_window.wnd_manage_emission_lines.show() self.ref_main_window.wnd_manage_emission_lines.activateWindow() def pb_fit_param_general_clicked(self): # Position the window in relation ot the main window (only when called once) pos = self.ref_main_window.pos() self.ref_main_window.wnd_general_fitting_settings.position_once( pos.x(), pos.y()) if not self.ref_main_window.wnd_general_fitting_settings.isVisible(): self.ref_main_window.wnd_general_fitting_settings.show() self.ref_main_window.wnd_general_fitting_settings.activateWindow() def pb_fit_param_shared_clicked(self): # Position the window in relation ot the main window (only when called once) pos = self.ref_main_window.pos() self.ref_main_window.wnd_fitting_parameters_shared.position_once( pos.x(), pos.y()) if not self.ref_main_window.wnd_fitting_parameters_shared.isVisible(): self.ref_main_window.wnd_fitting_parameters_shared.show() self.ref_main_window.wnd_fitting_parameters_shared.activateWindow() def pb_fit_param_lines_clicked(self): # Position the window in relation ot the main window (only when called once) pos = self.ref_main_window.pos() self.ref_main_window.wnd_fitting_parameters_lines.position_once( pos.x(), pos.y()) if not self.ref_main_window.wnd_fitting_parameters_lines.isVisible(): self.ref_main_window.wnd_fitting_parameters_lines.show() self.ref_main_window.wnd_fitting_parameters_lines.activateWindow() def pb_save_spectrum_clicked(self): current_dir = self.gpc.get_current_working_directory() dir = QFileDialog.getExistingDirectory( self, "Select Directory to Save Spectrum/Fit", current_dir, QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks, ) if dir: try: self.gpc.save_spectrum(dir, save_fit=self._fit_available) logger.debug(f"Spectrum/Fit is saved to directory {dir}") except Exception as ex: msg = str(ex) msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() else: logger.debug("Spectrum/Fit saving is cancelled") def pb_start_fitting_clicked(self): def cb(): try: self.gpc.total_spectrum_fitting() success, msg = True, "" except Exception as ex: success, msg = False, str(ex) return {"success": success, "msg": msg} self._compute_in_background(cb, self.slot_start_fitting_clicked) @Slot(object) def slot_start_fitting_clicked(self, result): self._recover_after_compute(self.slot_start_fitting_clicked) success = result["success"] if success: self._set_fit_status(True) else: msg = result["msg"] msgbox = QMessageBox(QMessageBox.Critical, "Failed to Fit Total Spectrum", msg, QMessageBox.Ok, parent=self) msgbox.exec() # Reload the table self.signal_total_spectrum_fitting_completed.emit(success) def _update_le_fitting_results(self): rf = self.gpc.compute_current_rfactor(self._fit_available) rf_text = f"{rf:.4f}" if rf is not None else "n/a" if self._fit_available: _ = self.gpc.get_iter_and_var_number() iter = _["iter_number"] nvar = _["var_number"] self.le_fitting_results.setText( f"Iterations: {iter} Variables: {nvar} R-factor: {rf_text}") else: self.le_fitting_results.setText(f"R-factor: {rf_text}") @Slot() def update_fit_status(self): self._fit_available = self.gui_vars["gui_state"][ "state_model_fit_exists"] self._update_le_fitting_results() @Slot() def clear_fit_status(self): # Clear fit status (reset it to False - no valid fit is available) self.gui_vars["gui_state"]["state_model_fit_exists"] = False self.update_fit_status() def _set_fit_status(self, status): self.gui_vars["gui_state"]["state_model_fit_exists"] = status self.update_fit_status() def _compute_in_background(self, func, slot, *args, **kwargs): """ Run function `func` in a background thread. Send the signal `self.computations_complete` once computation is finished. Parameters ---------- func: function Reference to a function that is supposed to be executed at the background. The function return value is passed as a signal parameter once computation is complete. slot: qtpy.QtCore.Slot or None Reference to a slot. If not None, then the signal `self.computation_complete` is connected to this slot. args, kwargs arguments of the function `func`. """ signal_complete = self.computations_complete def func_to_run(func, *args, **kwargs): class LoadFile(QRunnable): def run(self): result_dict = func(*args, **kwargs) signal_complete.emit(result_dict) return LoadFile() if slot is not None: self.computations_complete.connect(slot) self.gui_vars["gui_state"]["running_computations"] = True self.update_global_state.emit() QThreadPool.globalInstance().start(func_to_run(func, *args, **kwargs)) def _recover_after_compute(self, slot): """ The function should be called after the signal `self.computations_complete` is received. The slot should be the same as the one used when calling `self.compute_in_background`. """ if slot is not None: self.computations_complete.disconnect(slot) self.gui_vars["gui_state"]["running_computations"] = False self.update_global_state.emit()
class CompletionConfigPage(PluginConfigPage): def __init__(self, plugin, parent, providers=[]): super().__init__(plugin, parent) self.providers = providers def setup_page(self): newcb = self.create_checkbox # ------------------- Providers status group --------------------------- self.provider_checkboxes = [] providers_layout = QGridLayout() self.providers_group = QGroupBox(_("Providers")) for i, (provider_key, provider_name) in enumerate(self.providers): cb = newcb(_('Enable {0} provider').format(provider_name), ('enabled_providers', provider_key), default=True) providers_layout.addWidget(cb, i, 0) self.provider_checkboxes.append(cb) self.providers_group.setLayout(providers_layout) completions_wait_for_ms = self.create_spinbox( _("Time to wait for all providers to return (ms):"), None, 'completions_wait_for_ms', min_=0, max_=10000, step=10, tip=_("Beyond this timeout the first available provider " "will be returned")) completion_hint_box = newcb( _("Show completion details"), 'completions_hint', section='editor') automatic_completion_box = newcb( _("Show completions on the fly"), 'automatic_completions', section='editor') completions_after_characters = self.create_spinbox( _("Show automatic completions after characters entered:"), None, 'automatic_completions_after_chars', min_=1, step=1, tip=_("Default is 3"), section='editor') code_snippets_box = newcb( _("Enable code snippets"), 'enable_code_snippets') completions_after_idle = self.create_spinbox( _("Show automatic completions after keyboard idle (ms):"), None, 'automatic_completions_after_ms', min_=0, max_=10000, step=10, tip=_("Default is 300 milliseconds"), section='editor') completions_hint_after_idle = self.create_spinbox( _("Show completion details after keyboard idle (ms):"), None, 'completions_hint_after_ms', min_=0, max_=10000, step=10, tip=_("Default is 500 milliseconds"), section='editor') # ------------------- Completions group --------------------------- self.completions_group = QGroupBox(_('Completions')) completions_layout = QGridLayout() completions_layout.addWidget(completion_hint_box, 0, 0) completions_layout.addWidget(code_snippets_box, 1, 0) completions_layout.addWidget(automatic_completion_box, 2, 0) completions_layout.addWidget(completions_after_characters.plabel, 3, 0) completions_layout.addWidget( completions_after_characters.spinbox, 3, 1) completions_layout.addWidget(completions_after_idle.plabel, 4, 0) completions_layout.addWidget(completions_after_idle.spinbox, 4, 1) completions_layout.addWidget(completions_hint_after_idle.plabel, 5, 0) completions_layout.addWidget(completions_hint_after_idle.spinbox, 5, 1) completions_layout.addWidget(completions_wait_for_ms.plabel, 6, 0) completions_layout.addWidget(completions_wait_for_ms.spinbox, 6, 1) completions_layout.setColumnStretch(2, 6) self.completions_group.setLayout(completions_layout) def disable_completion_after_characters(state): completions_after_characters.plabel.setEnabled(state) completions_after_characters.spinbox.setEnabled(state) automatic_completion_box.toggled.connect( disable_completion_after_characters) layout = QVBoxLayout() layout.addWidget(self.completions_group) layout.addWidget(self.providers_group) layout.addStretch(1) self.setLayout(layout) def enable_disable_plugin(self, state): self.providers_group.setEnabled(state) self.completions_group.setEnabled(state) if self.tabs is not None: num_tabs = self.tabs.count() index = 1 while index < num_tabs: tab_widget = self.tabs.widget(index) tab_widget.setEnabled(state) index += 1
class RemoteKernelSetupDialog(QDialog): """Dialog to connect to existing kernels (either local or remote).""" def __init__(self, parent=None): super(RemoteKernelSetupDialog, self).__init__(parent) self.setWindowTitle(_('Setup remote kernel')) self.TEXT_FETCH_REMOTE_CONN_FILES_BTN = 'Fetch remote connection files' self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME = 'jupyter --runtime-dir' # Name of the connection cfg_name_label = QLabel(_('Configuration name:')) self.cfg_name_line_edit = QLineEdit() # SSH connection hostname_label = QLabel(_('Hostname:')) self.hostname_lineedit = QLineEdit() port_label = QLabel(_('Port:')) self.port_lineeidt = QLineEdit() self.port_lineeidt.setMaximumWidth(75) username_label = QLabel(_('Username:'******'Password:'******'SSH keyfile:')) self.pw = QLineEdit() self.pw.setEchoMode(QLineEdit.Password) self.pw_radio.toggled.connect(self.pw.setEnabled) self.keyfile_radio.toggled.connect(self.pw.setDisabled) self.keyfile_path_lineedit = QLineEdit() keyfile_browse_btn = QPushButton(_('Browse')) keyfile_browse_btn.clicked.connect(self.select_ssh_key) keyfile_layout = QHBoxLayout() keyfile_layout.addWidget(self.keyfile_path_lineedit) keyfile_layout.addWidget(keyfile_browse_btn) passphrase_label = QLabel(_('Passphrase:')) self.passphrase_lineedit = QLineEdit() self.passphrase_lineedit.setPlaceholderText(_('Optional')) self.passphrase_lineedit.setEchoMode(QLineEdit.Password) self.keyfile_radio.toggled.connect(self.keyfile_path_lineedit.setEnabled) self.keyfile_radio.toggled.connect(self.passphrase_lineedit.setEnabled) self.keyfile_radio.toggled.connect(keyfile_browse_btn.setEnabled) self.keyfile_radio.toggled.connect(passphrase_label.setEnabled) self.pw_radio.toggled.connect(self.keyfile_path_lineedit.setDisabled) self.pw_radio.toggled.connect(self.passphrase_lineedit.setDisabled) self.pw_radio.toggled.connect(keyfile_browse_btn.setDisabled) self.pw_radio.toggled.connect(passphrase_label.setDisabled) # Button to fetch JSON files listing # self.kf_fetch_conn_files_btn = QPushButton(_(self.TEXT_FETCH_REMOTE_CONN_FILES_BTN)) # self.kf_fetch_conn_files_btn.clicked.connect(self.fill_combobox_with_fetched_remote_connection_files) # self.cb_remote_conn_files = QComboBox() # self.cb_remote_conn_files.currentIndexChanged.connect(self._take_over_selected_remote_configuration_file) # Remote kernel groupbox self.start_remote_kernel_group = QGroupBox(_("Start remote kernel")) # Advanced settings to get remote connection files jupyter_runtime_location_cmd_label = QLabel(_('Command to get Jupyter runtime:')) self.jupyter_runtime_location_cmd_lineedit = QLineEdit() self.jupyter_runtime_location_cmd_lineedit.setPlaceholderText(_(self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME)) # SSH layout ssh_layout = QGridLayout() ssh_layout.addWidget(cfg_name_label, 0, 0) ssh_layout.addWidget(self.cfg_name_line_edit, 0, 2) ssh_layout.addWidget(hostname_label, 1, 0, 1, 2) ssh_layout.addWidget(self.hostname_lineedit, 1, 2) ssh_layout.addWidget(port_label, 1, 3) ssh_layout.addWidget(self.port_lineeidt, 1, 4) ssh_layout.addWidget(username_label, 2, 0, 1, 2) ssh_layout.addWidget(self.username_lineedit, 2, 2, 1, 3) # SSH authentication layout auth_layout = QGridLayout() auth_layout.addWidget(self.pw_radio, 1, 0) auth_layout.addWidget(pw_label, 1, 1) auth_layout.addWidget(self.pw, 1, 2) auth_layout.addWidget(self.keyfile_radio, 2, 0) auth_layout.addWidget(keyfile_label, 2, 1) auth_layout.addLayout(keyfile_layout, 2, 2) auth_layout.addWidget(passphrase_label, 3, 1) auth_layout.addWidget(self.passphrase_lineedit, 3, 2) auth_layout.addWidget(jupyter_runtime_location_cmd_label, 4, 1) auth_layout.addWidget(self.jupyter_runtime_location_cmd_lineedit, 4, 2) # auth_layout.addWidget(self.kf_fetch_conn_files_btn, 5, 1) # auth_layout.addWidget(self.cb_remote_conn_files, 5, 2) auth_group.setLayout(auth_layout) # Remote kernel layout self.rm_group = QGroupBox(_("Setup up of a remote connection")) self.rm_group.setEnabled(False) rm_layout = QVBoxLayout() rm_layout.addLayout(ssh_layout) rm_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) rm_layout.addWidget(auth_group) self.rm_group.setLayout(rm_layout) self.rm_group.setCheckable(False) # Ok and Cancel buttons self.accept_btns = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.accept_btns.accepted.connect(self.accept) self.accept_btns.rejected.connect(self.reject) btns_layout = QHBoxLayout() btns_layout.addWidget(self.accept_btns) # Dialog layout layout = QVBoxLayout() layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) # layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12))) layout.addWidget(self.rm_group) layout.addLayout(btns_layout) # Main layout hbox_layout = QHBoxLayout(self) # Left side with the list of all remote connection configurations items_label = QLabel(text="Configured remote locations") self.items_list = QListWidget() self.items_list.clicked.connect(self._on_items_list_click) items_layout = QVBoxLayout() items_layout.addWidget(items_label) items_layout.addWidget(self.items_list) edit_delete_new_buttons_layout = QHBoxLayout() edit_btn = QPushButton(text="Edit") add_btn = QPushButton(text="Add") delete_btn = QPushButton(text="Delete") add_btn.clicked.connect(self._on_add_btn_click) edit_btn.clicked.connect(self._on_edit_btn_click) delete_btn.clicked.connect(self._on_delete_btn_click) edit_delete_new_buttons_layout.addWidget(add_btn) edit_delete_new_buttons_layout.addWidget(edit_btn) edit_delete_new_buttons_layout.addWidget(delete_btn) items_layout.addLayout(edit_delete_new_buttons_layout) hbox_layout.addSpacerItem(QSpacerItem(10, 0)) hbox_layout.addLayout(items_layout) hbox_layout.addLayout(layout) self.lst_with_connecion_configs = [] def _on_items_list_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings idx_of_config = self.items_list.selectedIndexes()[0].row() cfg = self.lst_with_connecion_configs[idx_of_config] if isinstance(cfg, RemoteConnectionSettings): self._update_remote_connection_input_fields(cfg) else: show_info_dialog("Information", "This functionality is still not available") def _clear_remote_connection_input_fields(self): self.keyfile_path_lineedit.setText("") self.passphrase_lineedit.setText("") self.hostname_lineedit.setText("") self.username_lineedit.setText("") self.port_lineeidt.setText("") self.cfg_name_line_edit.setText("") self.jupyter_runtime_location_cmd_lineedit.setText("") self.keyfile_radio.setChecked(False) self.pw_radio.setChecked(False) def _update_remote_connection_input_fields(self, remote_conn_settings): self.keyfile_path_lineedit.setText(remote_conn_settings.keyfile_path) self.passphrase_lineedit.setText(remote_conn_settings.password) self.hostname_lineedit.setText(remote_conn_settings.hostname) self.username_lineedit.setText(remote_conn_settings.username) self.port_lineeidt.setText(str(remote_conn_settings.port)) self.cfg_name_line_edit.setText(remote_conn_settings.connection_name) self.jupyter_runtime_location_cmd_lineedit.setText(remote_conn_settings.cmd_for_jupyter_runtime_location) self.keyfile_radio.setChecked(remote_conn_settings.keyfile_path is not None) self.pw_radio.setChecked(remote_conn_settings.password is not None) def _on_add_btn_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings username = self.username_lineedit.text() passphrase = self.passphrase_lineedit.text() hostname = self.hostname_lineedit.text() keyfile_path = self.keyfile_path_lineedit.text() port = int(self.port_lineeidt.text()) if self.port_lineeidt.text() != "" else 22 jup_runtime_cmd = self.jupyter_runtime_location_cmd_lineedit.text() cfg_name = self.cfg_name_line_edit.text() cfg = RemoteConnectionSettings( username=username, hostname=hostname, keyfile_path=keyfile_path, port=port, connection_name=cfg_name, cmd_for_jupyter_runtime_location=jup_runtime_cmd, password=passphrase ) self.lst_with_connecion_configs.append(cfg) self._update_list_with_configs() self.rm_group.setEnabled(False) def _on_edit_btn_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings self.rm_group.setEnabled(True) idx_of_config = self.items_list.selectedIndexes()[0].row() cfg = self.lst_with_connecion_configs[idx_of_config] if isinstance(cfg, RemoteConnectionSettings): self._update_remote_connection_input_fields(cfg) else: show_info_dialog("Information", "This functionality is still not available") def _on_delete_btn_click(self): idx_of_config = self.items_list.selectedIndexes()[0].row() self.lst_with_connecion_configs.pop(idx_of_config) self._update_list_with_configs() def select_ssh_key(self): kf = getopenfilename(self, _('Select SSH keyfile'), get_home_dir(), '*.pem;;*')[0] self.keyfile_path_lineedit.setText(kf) def _take_over_selected_remote_configuration_file(self, chosen_idx_of_combobox_with_remote_conn_files): remote_path_filename = self.remote_conn_file_paths[chosen_idx_of_combobox_with_remote_conn_files] self.cf.setText(remote_path_filename) def set_connection_configs(self, lst_with_connecion_configs): self.lst_with_connecion_configs = lst_with_connecion_configs self._update_list_with_configs() def _update_list_with_configs(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings # now, fill the list self.items_list.clear() for cfg in self.lst_with_connecion_configs: if isinstance(cfg, LocalConnectionSettings): self.items_list.addItem(f"Local: {cfg.connection_name}") elif isinstance(cfg, RemoteConnectionSettings): self.items_list.addItem(f"Remote: {cfg.connection_name}") def get_connection_settings(self): return self.lst_with_connecion_configs
def setup_page(self): newcb = self.create_checkbox # Interface Group interface_group = QGroupBox(_("Interface")) banner_box = newcb(_("Display initial banner"), 'show_banner', tip=_("This option lets you hide the message " "shown at\nthe top of the console when " "it's opened.")) pager_box = newcb(_("Use a pager to display additional text inside " "the console"), 'use_pager', tip=_("Useful if you don't want to fill the " "console with long help or completion " "texts.\n" "Note: Use the Q key to get out of the " "pager.")) calltips_box = newcb(_("Show calltips"), 'show_calltips') ask_box = newcb(_("Ask for confirmation before closing"), 'ask_before_closing') reset_namespace_box = newcb( _("Ask for confirmation before removing all user-defined " "variables"), 'show_reset_namespace_warning', tip=_("This option lets you hide the warning message shown\n" "when resetting the namespace from Spyder.")) show_time_box = newcb(_("Show elapsed time"), 'show_elapsed_time') ask_restart_box = newcb( _("Ask for confirmation before restarting"), 'ask_before_restart', tip=_("This option lets you hide the warning message shown\n" "when restarting the kernel.")) interface_layout = QVBoxLayout() interface_layout.addWidget(banner_box) interface_layout.addWidget(pager_box) interface_layout.addWidget(calltips_box) interface_layout.addWidget(ask_box) interface_layout.addWidget(reset_namespace_box) interface_layout.addWidget(show_time_box) interface_layout.addWidget(ask_restart_box) interface_group.setLayout(interface_layout) comp_group = QGroupBox(_("Completion Type")) comp_label = QLabel(_("Decide what type of completion to use")) comp_label.setWordWrap(True) completers = [(_("Graphical"), 0), (_("Terminal"), 1), (_("Plain"), 2)] comp_box = self.create_combobox(_("Completion:")+" ", completers, 'completion_type') comp_layout = QVBoxLayout() comp_layout.addWidget(comp_label) comp_layout.addWidget(comp_box) comp_group.setLayout(comp_layout) # Source Code Group source_code_group = QGroupBox(_("Source code")) buffer_spin = self.create_spinbox( _("Buffer: "), _(" lines"), 'buffer_size', min_=-1, max_=1000000, step=100, tip=_("Set the maximum number of lines of text shown in the\n" "console before truncation. Specifying -1 disables it\n" "(not recommended!)")) source_code_layout = QVBoxLayout() source_code_layout.addWidget(buffer_spin) source_code_group.setLayout(source_code_layout) # --- Graphics --- # Pylab Group pylab_group = QGroupBox(_("Support for graphics (Matplotlib)")) pylab_box = newcb(_("Activate support"), 'pylab') autoload_pylab_box = newcb( _("Automatically load Pylab and NumPy modules"), 'pylab/autoload', tip=_("This lets you load graphics support without importing\n" "the commands to do plots. Useful to work with other\n" "plotting libraries different to Matplotlib or to develop\n" "GUIs with Spyder.")) autoload_pylab_box.setEnabled(self.get_option('pylab')) pylab_box.toggled.connect(autoload_pylab_box.setEnabled) pylab_layout = QVBoxLayout() pylab_layout.addWidget(pylab_box) pylab_layout.addWidget(autoload_pylab_box) pylab_group.setLayout(pylab_layout) # Pylab backend Group inline = _("Inline") automatic = _("Automatic") backend_group = QGroupBox(_("Graphics backend")) bend_label = QLabel(_("Decide how graphics are going to be displayed " "in the console. If unsure, please select " "<b>%s</b> to put graphics inside the " "console or <b>%s</b> to interact with " "them (through zooming and panning) in a " "separate window.") % (inline, automatic)) bend_label.setWordWrap(True) backends = [(inline, 0), (automatic, 1), ("Qt5", 2), ("Qt4", 3)] if sys.platform == 'darwin': backends.append(("OS X", 4)) if sys.platform.startswith('linux'): backends.append(("Gtk3", 5)) backends.append(("Gtk", 6)) if PY2: backends.append(("Wx", 7)) backends.append(("Tkinter", 8)) backends = tuple(backends) backend_box = self.create_combobox( _("Backend:") + " ", backends, 'pylab/backend', default=0, tip=_("This option will be applied the next time a console is " "opened.")) backend_layout = QVBoxLayout() backend_layout.addWidget(bend_label) backend_layout.addWidget(backend_box) backend_group.setLayout(backend_layout) backend_group.setEnabled(self.get_option('pylab')) pylab_box.toggled.connect(backend_group.setEnabled) # Inline backend Group inline_group = QGroupBox(_("Inline backend")) inline_label = QLabel(_("Decide how to render the figures created by " "this backend")) inline_label.setWordWrap(True) formats = (("PNG", 0), ("SVG", 1)) format_box = self.create_combobox(_("Format:")+" ", formats, 'pylab/inline/figure_format', default=0) resolution_spin = self.create_spinbox( _("Resolution:")+" ", " "+_("dpi"), 'pylab/inline/resolution', min_=50, max_=999, step=0.1, tip=_("Only used when the format is PNG. Default is " "72")) width_spin = self.create_spinbox( _("Width:")+" ", " "+_("inches"), 'pylab/inline/width', min_=2, max_=20, step=1, tip=_("Default is 6")) height_spin = self.create_spinbox( _("Height:")+" ", " "+_("inches"), 'pylab/inline/height', min_=1, max_=20, step=1, tip=_("Default is 4")) bbox_inches_box = newcb( _("Use a tight layout for inline plots"), 'pylab/inline/bbox_inches', tip=_("Sets bbox_inches to \"tight\" when\n" "plotting inline with matplotlib.\n" "When enabled, can cause discrepancies\n" "between the image displayed inline and\n" "that created using savefig.")) inline_v_layout = QVBoxLayout() inline_v_layout.addWidget(inline_label) inline_layout = QGridLayout() inline_layout.addWidget(format_box.label, 1, 0) inline_layout.addWidget(format_box.combobox, 1, 1) inline_layout.addWidget(resolution_spin.plabel, 2, 0) inline_layout.addWidget(resolution_spin.spinbox, 2, 1) inline_layout.addWidget(resolution_spin.slabel, 2, 2) inline_layout.addWidget(width_spin.plabel, 3, 0) inline_layout.addWidget(width_spin.spinbox, 3, 1) inline_layout.addWidget(width_spin.slabel, 3, 2) inline_layout.addWidget(height_spin.plabel, 4, 0) inline_layout.addWidget(height_spin.spinbox, 4, 1) inline_layout.addWidget(height_spin.slabel, 4, 2) inline_layout.addWidget(bbox_inches_box, 5, 0, 1, 4) inline_h_layout = QHBoxLayout() inline_h_layout.addLayout(inline_layout) inline_h_layout.addStretch(1) inline_v_layout.addLayout(inline_h_layout) inline_group.setLayout(inline_v_layout) inline_group.setEnabled(self.get_option('pylab')) pylab_box.toggled.connect(inline_group.setEnabled) # --- Startup --- # Run lines Group run_lines_group = QGroupBox(_("Run code")) run_lines_label = QLabel(_("You can run several lines of code when " "a console is started. Please introduce " "each one separated by semicolons and a " "space, for example:<br>" "<i>import os; import sys</i>")) run_lines_label.setWordWrap(True) run_lines_edit = self.create_lineedit(_("Lines:"), 'startup/run_lines', '', alignment=Qt.Horizontal) run_lines_layout = QVBoxLayout() run_lines_layout.addWidget(run_lines_label) run_lines_layout.addWidget(run_lines_edit) run_lines_group.setLayout(run_lines_layout) # Run file Group run_file_group = QGroupBox(_("Run a file")) run_file_label = QLabel(_("You can also run a whole file at startup " "instead of just some lines (This is " "similar to have a PYTHONSTARTUP file).")) run_file_label.setWordWrap(True) file_radio = newcb(_("Use the following file:"), 'startup/use_run_file', False) run_file_browser = self.create_browsefile('', 'startup/run_file', '') run_file_browser.setEnabled(False) file_radio.toggled.connect(run_file_browser.setEnabled) run_file_layout = QVBoxLayout() run_file_layout.addWidget(run_file_label) run_file_layout.addWidget(file_radio) run_file_layout.addWidget(run_file_browser) run_file_group.setLayout(run_file_layout) # ---- Advanced settings ---- # Enable Jedi completion jedi_group = QGroupBox(_("Jedi completion")) jedi_label = QLabel(_("Enable Jedi-based <tt>Tab</tt> completion " "in the IPython console; similar to the " "greedy completer, but without evaluating " "the code.<br>" "<b>Warning:</b> Slows down your console " "when working with large dataframes!")) jedi_label.setWordWrap(True) jedi_box = newcb(_("Use Jedi completion in the IPython console"), "jedi_completer", tip=_("<b>Warning</b>: " "Slows down your console when working with " "large dataframes!<br>" "Allows completion of nested lists etc.")) jedi_layout = QVBoxLayout() jedi_layout.addWidget(jedi_label) jedi_layout.addWidget(jedi_box) jedi_group.setLayout(jedi_layout) # Greedy completer group greedy_group = QGroupBox(_("Greedy completion")) greedy_label = QLabel(_("Enable <tt>Tab</tt> completion on elements " "of lists, results of function calls, etc, " "<i>without</i> assigning them to a variable, " "like <tt>li[0].<Tab></tt> or " "<tt>ins.meth().<Tab></tt> <br>" "<b>Warning:</b> Due to a bug, IPython's " "greedy completer requires a leading " "<tt><Space></tt> for some completions; " "e.g. <tt>np.sin(<Space>np.<Tab>" "</tt> works while <tt>np.sin(np.<Tab> " "</tt> doesn't.")) greedy_label.setWordWrap(True) greedy_box = newcb(_("Use greedy completion in the IPython console"), "greedy_completer", tip="<b>Warning</b>: It can be unsafe because the " "code is actually evaluated when you press " "<tt>Tab</tt>.") greedy_layout = QVBoxLayout() greedy_layout.addWidget(greedy_label) greedy_layout.addWidget(greedy_box) greedy_group.setLayout(greedy_layout) # Autocall group autocall_group = QGroupBox(_("Autocall")) autocall_label = QLabel(_("Autocall makes IPython automatically call " "any callable object even if you didn't " "type explicit parentheses.<br>" "For example, if you type <i>str 43</i> it " "becomes <i>str(43)</i> automatically.")) autocall_label.setWordWrap(True) smart = _('Smart') full = _('Full') autocall_opts = ((_('Off'), 0), (smart, 1), (full, 2)) autocall_box = self.create_combobox( _("Autocall: "), autocall_opts, 'autocall', default=0, tip=_("On <b>%s</b> mode, Autocall is not applied if " "there are no arguments after the callable. On " "<b>%s</b> mode, all callable objects are " "automatically called (even if no arguments are " "present).") % (smart, full)) autocall_layout = QVBoxLayout() autocall_layout.addWidget(autocall_label) autocall_layout.addWidget(autocall_box) autocall_group.setLayout(autocall_layout) # Sympy group sympy_group = QGroupBox(_("Symbolic Mathematics")) sympy_label = QLabel(_("Perfom symbolic operations in the console " "(e.g. integrals, derivatives, vector " "calculus, etc) and get the outputs in a " "beautifully printed style (it requires the " "Sympy module).")) sympy_label.setWordWrap(True) sympy_box = newcb(_("Use symbolic math"), "symbolic_math", tip=_("This option loads the Sympy library to work " "with.<br>Please refer to its documentation " "to learn how to use it.")) sympy_layout = QVBoxLayout() sympy_layout.addWidget(sympy_label) sympy_layout.addWidget(sympy_box) sympy_group.setLayout(sympy_layout) # Prompts group prompts_group = QGroupBox(_("Prompts")) prompts_label = QLabel(_("Modify how Input and Output prompts are " "shown in the console.")) prompts_label.setWordWrap(True) in_prompt_edit = self.create_lineedit( _("Input prompt:"), 'in_prompt', '', _('Default is<br>' 'In [<span class="in-prompt-number">' '%i</span>]:'), alignment=Qt.Horizontal) out_prompt_edit = self.create_lineedit( _("Output prompt:"), 'out_prompt', '', _('Default is<br>' 'Out[<span class="out-prompt-number">' '%i</span>]:'), alignment=Qt.Horizontal) prompts_layout = QVBoxLayout() prompts_layout.addWidget(prompts_label) prompts_g_layout = QGridLayout() prompts_g_layout.addWidget(in_prompt_edit.label, 0, 0) prompts_g_layout.addWidget(in_prompt_edit.textbox, 0, 1) prompts_g_layout.addWidget(out_prompt_edit.label, 1, 0) prompts_g_layout.addWidget(out_prompt_edit.textbox, 1, 1) prompts_layout.addLayout(prompts_g_layout) prompts_group.setLayout(prompts_layout) # Windows adjustments windows_group = QGroupBox(_("Windows adjustments")) hide_cmd_windows = newcb( _("Hide command line output windows " "generated by the subprocess module."), 'hide_cmd_windows') windows_layout = QVBoxLayout() windows_layout.addWidget(hide_cmd_windows) windows_group.setLayout(windows_layout) # --- Tabs organization --- tabs = QTabWidget() tabs.addTab(self.create_tab(interface_group, comp_group, source_code_group), _("Display")) tabs.addTab(self.create_tab(pylab_group, backend_group, inline_group), _("Graphics")) tabs.addTab(self.create_tab(run_lines_group, run_file_group), _("Startup")) tabs.addTab(self.create_tab(jedi_group, greedy_group, autocall_group, sympy_group, prompts_group, windows_group), _("Advanced Settings")) vlayout = QVBoxLayout() vlayout.addWidget(tabs) self.setLayout(vlayout)
class ProjectLauncher(EasyDialog): NAME = _("Select project") sigOpenProject = Signal(bool, str) def __init__(self, parent=None): EasyDialog.__init__(self, parent) self.setup_page() def setup_page(self): lbl_select = QLabel(_('Select method:')) self.rb_new = QRadioButton(_('Create a new project')) self.rb_old = QRadioButton(_('Open an existing project')) hbox = QHBoxLayout() hbox.addWidget(self.rb_new) hbox.addWidget(self.rb_old) self.layout.addWidget(lbl_select) self.layout.addLayout(hbox) self.new_group = QGroupBox(_("New project")) self.new_project_name = self.create_lineedit("Project name", alignment=Qt.Horizontal) self.new_project_name.edit.setText(_("test")) self.new_project_path = self.create_browsedir("Project path") self.new_project_path.lineedit.edit.setText(os.getcwd()) new_layout = QVBoxLayout() new_layout.addWidget(self.new_project_name) new_layout.addWidget(self.new_project_path) self.new_group.setLayout(new_layout) self.old_group = QGroupBox(_("Existing project")) self.old_project_name = self.create_browsefile( "Project name", filters="EZD files (*.ezd);;All Files (*)") text = os.path.join(os.getcwd(), 'fake.ezd') self.old_project_name.lineedit.edit.setText(text) old_layout = QVBoxLayout() old_layout.addWidget(self.old_project_name) self.old_group.setLayout(old_layout) btnApply = QPushButton(_('Next')) btnApply.clicked.connect(self.apply) btnClose = QPushButton(_('Exit')) btnClose.clicked.connect(self.stop) hbox = QHBoxLayout() hbox.addWidget(btnApply) hbox.addWidget(btnClose) self.layout.addWidget(self.new_group) self.layout.addWidget(self.old_group) self.layout.addLayout(hbox) self.rb_new.toggled.connect(self.toggle) self.rb_old.toggled.connect(self.toggle) self.rb_old.setChecked(True) def toggle(self): if self.rb_new.isChecked(): self.new_group.setEnabled(True) self.old_group.setEnabled(False) elif self.rb_old.isChecked(): self.new_group.setEnabled(False) self.old_group.setEnabled(True) else: raise ValueError("Unknown value") def apply(self): if self.rb_new.isChecked(): new = True name = self.new_project_name.edit.text() path = self.new_project_path.lineedit.edit.text() afn = os.path.join(path, name) elif self.rb_old.isChecked(): new = False afn = self.old_project_name.lineedit.edit.text() else: raise ValueError("Unknown value") self.sigOpenProject.emit(new, afn) self.close() def stop(self): self.close() if self.parent is not None: self.parent.force_exit()
class FitMapsWidget(FormBaseWidget): # Signal that is sent (to main window) to update global state of the program update_global_state = Signal() computations_complete = Signal(object) signal_map_fitting_complete = Signal() signal_activate_tab_xrf_maps = Signal() def __init__(self, *, gpc, gui_vars): super().__init__() # Global processing classes self.gpc = gpc # Global GUI variables (used for control of GUI state) self.gui_vars = gui_vars # Reference to the main window. The main window will hold # references to all non-modal windows that could be opened # from multiple places in the program. self.ref_main_window = self.gui_vars["ref_main_window"] self.update_global_state.connect(self.ref_main_window.update_widget_state) self.initialize() def initialize(self): v_spacing = global_gui_parameters["vertical_spacing_in_tabs"] self._setup_settings() self._setup_start_fitting() self._setup_compute_roi_maps() self._setup_save_results() self._setup_quantitative_analysis() vbox = QVBoxLayout() vbox.addWidget(self.group_settings) vbox.addSpacing(v_spacing) vbox.addWidget(self.pb_start_map_fitting) vbox.addWidget(self.pb_compute_roi_maps) vbox.addSpacing(v_spacing) vbox.addWidget(self.group_save_results) vbox.addWidget(self.group_quant_analysis) self.setLayout(vbox) self._set_tooltips() # Timer is currently used to simulate processing self._timer = None self._timer_counter = 0 def _setup_settings(self): self.group_settings = QGroupBox("Options") self._dset_n_rows = 1 self._dset_n_cols = 1 self._area_row_min = 1 self._area_row_max = 1 self._area_col_min = 1 self._area_col_max = 1 self._validator_selected_area = IntValidatorStrict() self.le_start_row = LineEditExtended() self.le_start_row.textChanged.connect(self.le_start_row_text_changed) self.le_start_row.editingFinished.connect(self.le_start_row_editing_finished) self.le_start_col = LineEditExtended() self.le_start_col.textChanged.connect(self.le_start_col_text_changed) self.le_start_col.editingFinished.connect(self.le_start_col_editing_finished) self.le_end_row = LineEditExtended() self.le_end_row.textChanged.connect(self.le_end_row_text_changed) self.le_end_row.editingFinished.connect(self.le_end_row_editing_finished) self.le_end_col = LineEditExtended() self.le_end_col.textChanged.connect(self.le_end_col_text_changed) self.le_end_col.editingFinished.connect(self.le_end_col_editing_finished) self.group_save_plots = QGroupBox("Save spectra for pixels in the selected region") self.group_save_plots.setCheckable(True) self.group_save_plots.setChecked(False) self.group_save_plots.toggled.connect(self.group_save_plots_toggled) vbox = QVBoxLayout() grid = QGridLayout() grid.addWidget(QLabel("Start row:"), 0, 0) grid.addWidget(self.le_start_row, 0, 1) grid.addWidget(QLabel("column:"), 0, 2) grid.addWidget(self.le_start_col, 0, 3) grid.addWidget(QLabel("End row:"), 1, 0) grid.addWidget(self.le_end_row, 1, 1) grid.addWidget(QLabel("column:"), 1, 2) grid.addWidget(self.le_end_col, 1, 3) vbox.addLayout(grid) self.group_save_plots.setLayout(vbox) vbox = QVBoxLayout() vbox.addWidget(self.group_save_plots) self.group_settings.setLayout(vbox) def _setup_start_fitting(self): self.pb_start_map_fitting = QPushButton("Start XRF Map Fitting") self.pb_start_map_fitting.clicked.connect(self.pb_start_map_fitting_clicked) def _setup_compute_roi_maps(self): self.pb_compute_roi_maps = QPushButton("Compute XRF Maps Based on ROI ...") self.pb_compute_roi_maps.clicked.connect(self.pb_compute_roi_maps_clicked) def _setup_save_results(self): self.group_save_results = QGroupBox("Save Results") self.pb_save_to_db = QPushButton("Save to Database (Databroker) ...") self.pb_save_to_db.setEnabled(False) self.pb_save_q_calibration = QPushButton("Save Quantitative Calibration ...") self.pb_save_q_calibration.clicked.connect(self.pb_save_q_calibration_clicked) self.pb_export_to_tiff_and_txt = QPushButton("Export to TIFF and TXT ...") self.pb_export_to_tiff_and_txt.clicked.connect(self.pb_export_to_tiff_and_txt_clicked) grid = QGridLayout() grid.addWidget(self.pb_save_to_db, 0, 0, 1, 2) grid.addWidget(self.pb_save_q_calibration, 1, 0, 1, 2) grid.addWidget(self.pb_export_to_tiff_and_txt, 2, 0, 1, 2) self.group_save_results.setLayout(grid) def _setup_quantitative_analysis(self): self.group_quant_analysis = QGroupBox("Quantitative Analysis") self.pb_load_quant_calib = QPushButton("Load Quantitative Calibration ...") self.pb_load_quant_calib.clicked.connect(self.pb_load_quant_calib_clicked) vbox = QVBoxLayout() vbox.addWidget(self.pb_load_quant_calib) self.group_quant_analysis.setLayout(vbox) def _set_tooltips(self): set_tooltip( self.group_settings, "Raw spectra of individual pixels are saved as <b>.png</b> files for " "the selected region of the map." "The region is selected by specifying the <b>Start</b> and <b>End</b> coordinates " "(ranges of rows and columns) in pixels. The first and last rows and columns are " "included in the selection.", ) set_tooltip( self.le_start_row, "Number of the <b>first row</b> of the map to be included in the selection. " "The number must be less than the number entered into 'End row' box.", ) set_tooltip( self.le_start_col, "Number of the <b>first column</b> of the map to be included in the selection. " "The number must be less than the number entered into 'End column' box.", ) set_tooltip( self.le_end_row, "Number of the <b>last row</b> included in the selection. " "The number must be greater than the number entered into 'Start row' box.", ) set_tooltip( self.le_end_col, "Number of the <b>last column</b> included in the selection. " "The number must be greater than the number entered into 'Start column' box.", ) set_tooltip( self.pb_start_map_fitting, "Click to start <b>fitting of the XRF Maps</b>. The generated XRF Maps can be viewed " "in <b>'XRF Maps' tab</b>", ) set_tooltip( self.pb_compute_roi_maps, "Opens the window for setting up <b>spectral ROIs</b> and computating XRF Maps based on the ROIs", ) set_tooltip(self.pb_save_to_db, "Save generated XRF Maps to a <b>database</b> via Databroker") set_tooltip( self.pb_save_q_calibration, "Opens a Dialog Box which allows to preview and save <b>Quantitative Calibration data</b>", ) set_tooltip( self.pb_export_to_tiff_and_txt, "Open a Dialog box which allows to export XRF Maps as <b>TIFF</b> and <b>TXT</b> files", ) set_tooltip( self.pb_load_quant_calib, "Open a window with GUI tools for loading and managing previously saved " "<b>Quantitative Calibration data</b> used for processing (normalization) " "of XRF Maps. The loaded calibration data is applied to XRF Maps if 'Quantitative' " "box is checked in 'XRF Maps' tab", ) def update_widget_state(self, condition=None): if condition == "tooltips": self._set_tooltips() state_file_loaded = self.gui_vars["gui_state"]["state_file_loaded"] state_model_exist = self.gui_vars["gui_state"]["state_model_exists"] state_xrf_map_exists = self.gui_vars["gui_state"]["state_xrf_map_exists"] self.group_settings.setEnabled(state_file_loaded & state_model_exist) self.pb_start_map_fitting.setEnabled(state_file_loaded & state_model_exist) self.pb_compute_roi_maps.setEnabled(state_file_loaded & state_model_exist) self.group_save_results.setEnabled(state_xrf_map_exists) self.group_quant_analysis.setEnabled(state_xrf_map_exists) def slot_update_for_new_loaded_run(self): self.gpc.set_enable_save_spectra(False) selected_area = {"row_min": 1, "row_max": 1, "col_min": 1, "col_max": 1} self.gpc.set_selection_area_save_spectra(selected_area) self._update_area_selection_controls() def pb_compute_roi_maps_clicked(self): # Position the window in relation ot the main window (only when called once) pos = self.ref_main_window.pos() self.ref_main_window.wnd_compute_roi_maps.position_once(pos.x(), pos.y()) if not self.ref_main_window.wnd_compute_roi_maps.isVisible(): self.ref_main_window.wnd_compute_roi_maps.show() self.ref_main_window.wnd_compute_roi_maps.activateWindow() def pb_save_q_calibration_clicked(self): msg = "" if not self.gpc.is_quant_standard_selected(): # This is a safeguard. The button should be disabled if no standard is selected. msg += ( "No quantitative standard is selected. " "Use 'Load Quantitative Standard ...' button in 'Model' tab" ) if not self.gpc.is_quant_standard_fitting_available(): msg = msg + "\n" if msg else msg msg += ( "Select a dataset containing fitted XRF maps for the quantitative standard " "in XRF Maps tab (dataset name must end with 'fit')." ) if msg: msgbox = QMessageBox( QMessageBox.Information, "Additional Steps Needed", msg, QMessageBox.Ok, parent=self ) msgbox.exec() return try: params = self.gpc.get_displayed_quant_calib_parameters() distance_to_sample = params["distance_to_sample"] file_name = params["suggested_file_name"] preview = params["preview"] file_dir = self.gpc.get_current_working_directory() file_dir = os.path.expanduser(file_dir) file_path = os.path.join(file_dir, file_name) dlg = DialogSaveCalibration(file_path=file_path) dlg.distance_to_sample = distance_to_sample dlg.preview = preview res = dlg.exec() if res: self.gpc.save_quantitative_standard(dlg.file_path, dlg.overwrite_existing) logger.info(f"Quantitative calibration was saved to the file '{dlg.file_path}'") else: logger.info("Saving quantitative calibration was cancelled.") # We want to save distance to sample even if saving was cancelled self.gpc.save_distance_to_sample(dlg.distance_to_sample) except Exception as ex: msg = str(ex) msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() def pb_export_to_tiff_and_txt_clicked(self): # TODO: Propagate full path to the saved file here dir_path = self.gpc.get_current_working_directory() dir_path = os.path.expanduser(dir_path) params = self.gpc.get_parameters_for_exporting_maps() dlg = DialogExportToTiffAndTxt(dir_path=dir_path) dlg.dset_list = params["dset_list"] dlg.dset_sel = params["dset_sel"] dlg.scaler_list = params["scaler_list"] dlg.scaler_sel = params["scaler_sel"] dlg.interpolate_on = params["interpolate_on"] dlg.quant_norm_on = params["quant_norm_on"] res = dlg.exec() if res: try: result_path = dlg.dir_path dataset_name = dlg.get_selected_dset_name() scaler_name = dlg.get_selected_scaler_name() interpolate_on = dlg.interpolate_on quant_norm_on = dlg.quant_norm_on file_formats = [] if dlg.save_tiff: file_formats.append("tiff") if dlg.save_txt: file_formats.append("txt") self.gpc.export_xrf_maps( results_path=result_path, dataset_name=dataset_name, scaler_name=scaler_name, interpolate_on=interpolate_on, quant_norm_on=quant_norm_on, file_formats=file_formats, ) if file_formats: formats_text = " and ".join([_.upper() for _ in file_formats]) else: formats_text = "No" msg = f"{formats_text} files were saved to the directory '{result_path}'" logger.info(msg) msgbox = QMessageBox(QMessageBox.Information, "Files Saved", msg, QMessageBox.Ok, parent=self) msgbox.exec() except Exception as ex: msg = str(ex) msgbox = QMessageBox(QMessageBox.Critical, "Error", msg, QMessageBox.Ok, parent=self) msgbox.exec() def pb_load_quant_calib_clicked(self): # Position the window in relation ot the main window (only when called once) pos = self.ref_main_window.pos() self.ref_main_window.wnd_load_quantitative_calibration.position_once(pos.x(), pos.y()) if not self.ref_main_window.wnd_load_quantitative_calibration.isVisible(): self.ref_main_window.wnd_load_quantitative_calibration.show() self.ref_main_window.wnd_load_quantitative_calibration.activateWindow() def pb_start_map_fitting_clicked(self): def cb(): try: self.gpc.fit_individual_pixels() success, msg = True, "" except Exception as ex: success, msg = False, str(ex) return {"success": success, "msg": msg} self._compute_in_background(cb, self.slot_start_map_fitting_clicked) @Slot(object) def slot_start_map_fitting_clicked(self, result): self._recover_after_compute(self.slot_start_map_fitting_clicked) success = result["success"] if success: self.gui_vars["gui_state"]["state_xrf_map_exists"] = True else: msg = result["msg"] msgbox = QMessageBox( QMessageBox.Critical, "Failed to Fit Individual Pixel Spectra", msg, QMessageBox.Ok, parent=self ) msgbox.exec() self.signal_map_fitting_complete.emit() self.update_global_state.emit() if success: self.signal_activate_tab_xrf_maps.emit() """ @Slot() def timerExpired(self): self._timer_counter += 1 progress_bar = self.ref_main_window.statusProgressBar progress_bar.setValue(self._timer_counter) if self._timer_counter >= 100: self._timer.stop() self._timer.timeout.disconnect(self.timerExpired) self._timer = None progress_bar.setValue(0) status_bar = self.ref_main_window.statusBar() status_bar.showMessage("XRF Maps are generated. " "Results are presented in 'XRF Maps' tab.", 5000) self.gui_vars["gui_state"]["running_computations"] = False self.update_global_state.emit() """ def group_save_plots_toggled(self, state): self.gpc.set_enable_save_spectra(state) def le_start_row_text_changed(self, text): valid = self._validate_row_number(text) and int(text) <= self._area_row_max self.le_start_row.setValid(valid) def le_start_row_editing_finished(self): text = self.le_start_row.text() valid = self._validate_row_number(text) and int(text) <= self._area_row_max if valid: self._save_selected_area(row_min=int(text)) else: self._show_selected_area() def le_end_row_text_changed(self, text): valid = self._validate_row_number(text) and int(text) >= self._area_row_min self.le_end_row.setValid(valid) def le_end_row_editing_finished(self): text = self.le_end_row.text() valid = self._validate_row_number(text) and int(text) >= self._area_row_min if valid: self._save_selected_area(row_max=int(text)) else: self._show_selected_area() def le_start_col_text_changed(self, text): valid = self._validate_col_number(text) and int(text) <= self._area_col_max self.le_start_col.setValid(valid) def le_start_col_editing_finished(self): text = self.le_start_col.text() valid = self._validate_col_number(text) and int(text) <= self._area_col_max if valid: self._save_selected_area(col_min=int(text)) else: self._show_selected_area() def le_end_col_text_changed(self, text): valid = self._validate_col_number(text) and int(text) >= self._area_col_min self.le_end_col.setValid(valid) def le_end_col_editing_finished(self): text = self.le_end_col.text() valid = self._validate_col_number(text) and int(text) >= self._area_col_min if valid: self._save_selected_area(col_max=int(text)) else: self._show_selected_area() def _validate_row_number(self, value_str): if self._validator_selected_area.validate(value_str, 0)[0] != IntValidatorStrict.Acceptable: return False value = int(value_str) if 1 <= value <= self._dset_n_rows: return True else: return False def _validate_col_number(self, value_str): if self._validator_selected_area.validate(value_str, 0)[0] != IntValidatorStrict.Acceptable: return False value = int(value_str) if 1 <= value <= self._dset_n_cols: return True else: return False def _update_area_selection_controls(self): map_size = self.gpc.get_dataset_map_size() map_size = (1, 1) if map_size is None else map_size self._dset_n_rows, self._dset_n_cols = map_size self.group_save_plots.setChecked(self.gpc.get_enable_save_spectra()) area = self.gpc.get_selection_area_save_spectra() self._area_row_min = area["row_min"] self._area_row_max = area["row_max"] self._area_col_min = area["col_min"] self._area_col_max = area["col_max"] self._show_selected_area() def _show_selected_area(self): self.le_start_row.setText(f"{self._area_row_min}") self.le_end_row.setText(f"{self._area_row_max}") self.le_start_col.setText(f"{self._area_col_min}") self.le_end_col.setText(f"{self._area_col_max}") def _save_selected_area(self, row_min=None, row_max=None, col_min=None, col_max=None): if row_min is not None: self._area_row_min = row_min if row_max is not None: self._area_row_max = row_max if col_min is not None: self._area_col_min = col_min if col_max is not None: self._area_col_max = col_max area = { "row_min": self._area_row_min, "row_max": self._area_row_max, "col_min": self._area_col_min, "col_max": self._area_col_max, } self.gpc.set_selection_area_save_spectra(area) self._update_area_selection_controls() def _compute_in_background(self, func, slot, *args, **kwargs): """ Run function `func` in a background thread. Send the signal `self.computations_complete` once computation is finished. Parameters ---------- func: function Reference to a function that is supposed to be executed at the background. The function return value is passed as a signal parameter once computation is complete. slot: qtpy.QtCore.Slot or None Reference to a slot. If not None, then the signal `self.computation_complete` is connected to this slot. args, kwargs arguments of the function `func`. """ signal_complete = self.computations_complete def func_to_run(func, *args, **kwargs): class LoadFile(QRunnable): def run(self): result_dict = func(*args, **kwargs) signal_complete.emit(result_dict) return LoadFile() if slot is not None: self.computations_complete.connect(slot) self.gui_vars["gui_state"]["running_computations"] = True self.update_global_state.emit() QThreadPool.globalInstance().start(func_to_run(func, *args, **kwargs)) def _recover_after_compute(self, slot): """ The function should be called after the signal `self.computations_complete` is received. The slot should be the same as the one used when calling `self.compute_in_background`. """ if slot is not None: self.computations_complete.disconnect(slot) self.gui_vars["gui_state"]["running_computations"] = False self.update_global_state.emit()
def setup_page(self): interface_group = QGroupBox(_("Interface")) newcb = self.create_checkbox singletab_box = newcb(_("One tab per script"), 'single_tab') showtime_box = newcb(_("Show elapsed time"), 'show_elapsed_time') icontext_box = newcb(_("Show icons and text"), 'show_icontext') # Interface Group interface_layout = QVBoxLayout() interface_layout.addWidget(singletab_box) interface_layout.addWidget(showtime_box) interface_layout.addWidget(icontext_box) interface_group.setLayout(interface_layout) # Source Code Group display_group = QGroupBox(_("Source code")) buffer_spin = self.create_spinbox( _("Buffer: "), _(" lines"), 'max_line_count', min_=0, max_=1000000, step=100, tip=_("Set maximum line count")) wrap_mode_box = newcb(_("Wrap lines"), 'wrap') merge_channels_box = newcb( _("Merge process standard output/error channels"), 'merge_output_channels', tip=_("Merging the output channels of the process means that\n" "the standard error won't be written in red anymore,\n" "but this has the effect of speeding up display.")) colorize_sys_stderr_box = newcb( _("Colorize standard error channel using ANSI escape codes"), 'colorize_sys_stderr', tip=_("This method is the only way to have colorized standard\n" "error channel when the output channels have been " "merged.")) merge_channels_box.toggled.connect(colorize_sys_stderr_box.setEnabled) merge_channels_box.toggled.connect(colorize_sys_stderr_box.setChecked) colorize_sys_stderr_box.setEnabled( self.get_option('merge_output_channels')) display_layout = QVBoxLayout() display_layout.addWidget(buffer_spin) display_layout.addWidget(wrap_mode_box) display_layout.addWidget(merge_channels_box) display_layout.addWidget(colorize_sys_stderr_box) display_group.setLayout(display_layout) # Background Color Group bg_group = QGroupBox(_("Background color")) bg_label = QLabel(_("This option will be applied the next time " "a Python console or a terminal is opened.")) bg_label.setWordWrap(True) lightbg_box = newcb(_("Light background (white color)"), 'light_background') bg_layout = QVBoxLayout() bg_layout.addWidget(bg_label) bg_layout.addWidget(lightbg_box) bg_group.setLayout(bg_layout) # Advanced settings source_group = QGroupBox(_("Source code")) completion_box = newcb(_("Automatic code completion"), 'codecompletion/auto') case_comp_box = newcb(_("Case sensitive code completion"), 'codecompletion/case_sensitive') comp_enter_box = newcb(_("Enter key selects completion"), 'codecompletion/enter_key') calltips_box = newcb(_("Display balloon tips"), 'calltips') source_layout = QVBoxLayout() source_layout.addWidget(completion_box) source_layout.addWidget(case_comp_box) source_layout.addWidget(comp_enter_box) source_layout.addWidget(calltips_box) source_group.setLayout(source_layout) # PYTHONSTARTUP replacement pystartup_group = QGroupBox(_("PYTHONSTARTUP replacement")) pystartup_bg = QButtonGroup(pystartup_group) pystartup_label = QLabel(_("This option will override the " "PYTHONSTARTUP environment variable which\n" "defines the script to be executed during " "the Python console startup.")) def_startup_radio = self.create_radiobutton( _("Default PYTHONSTARTUP script"), 'pythonstartup/default', button_group=pystartup_bg) cus_startup_radio = self.create_radiobutton( _("Use the following startup script:"), 'pythonstartup/custom', button_group=pystartup_bg) pystartup_file = self.create_browsefile('', 'pythonstartup', '', filters=_("Python scripts")+\ " (*.py)") def_startup_radio.toggled.connect(pystartup_file.setDisabled) cus_startup_radio.toggled.connect(pystartup_file.setEnabled) pystartup_layout = QVBoxLayout() pystartup_layout.addWidget(pystartup_label) pystartup_layout.addWidget(def_startup_radio) pystartup_layout.addWidget(cus_startup_radio) pystartup_layout.addWidget(pystartup_file) pystartup_group.setLayout(pystartup_layout) # Monitor Group monitor_group = QGroupBox(_("Monitor")) monitor_label = QLabel(_("The monitor provides introspection " "features to console: code completion, " "calltips and variable explorer. " "Because it relies on several modules, " "disabling the monitor may be useful " "to accelerate console startup.")) monitor_label.setWordWrap(True) monitor_box = newcb(_("Enable monitor"), 'monitor/enabled') for obj in (completion_box, case_comp_box, comp_enter_box, calltips_box): monitor_box.toggled.connect(obj.setEnabled) obj.setEnabled(self.get_option('monitor/enabled')) monitor_layout = QVBoxLayout() monitor_layout.addWidget(monitor_label) monitor_layout.addWidget(monitor_box) monitor_group.setLayout(monitor_layout) # Qt Group opts = [ (_("Default library"), 'default'), ('PyQt5', 'pyqt5'), ('PyQt4', 'pyqt'), ('PySide', 'pyside'), ] qt_group = QGroupBox(_("Qt-Python Bindings")) qt_setapi_box = self.create_combobox( _("Library:") + " ", opts, 'qt/api', default='default', tip=_("This option will act on<br> " "libraries such as Matplotlib, guidata " "or ETS")) qt_layout = QVBoxLayout() qt_layout.addWidget(qt_setapi_box) qt_group.setLayout(qt_layout) # Matplotlib Group mpl_group = QGroupBox(_("Graphics")) mpl_label = QLabel(_("Decide which backend to use to display graphics. " "If unsure, please select the <b>Automatic</b> " "backend.<br><br>" "<b>Note:</b> We support a very limited number " "of backends in our Python consoles. If you " "prefer to work with a different one, please use " "an IPython console.")) mpl_label.setWordWrap(True) backends = [(_("Automatic"), 0), (_("None"), 1)] if not os.name == 'nt' and programs.is_module_installed('_tkinter'): backends.append( ("Tkinter", 2) ) backends = tuple(backends) mpl_backend_box = self.create_combobox( _("Backend:")+" ", backends, 'matplotlib/backend/value', tip=_("This option will be applied the " "next time a console is opened.")) mpl_installed = programs.is_module_installed('matplotlib') mpl_layout = QVBoxLayout() mpl_layout.addWidget(mpl_label) mpl_layout.addWidget(mpl_backend_box) mpl_group.setLayout(mpl_layout) mpl_group.setEnabled(mpl_installed) # ETS Group ets_group = QGroupBox(_("Enthought Tool Suite")) ets_label = QLabel(_("Enthought Tool Suite (ETS) supports " "PyQt4 (qt4) and wxPython (wx) graphical " "user interfaces.")) ets_label.setWordWrap(True) ets_edit = self.create_lineedit(_("ETS_TOOLKIT:"), 'ets_backend', alignment=Qt.Horizontal) ets_layout = QVBoxLayout() ets_layout.addWidget(ets_label) ets_layout.addWidget(ets_edit) ets_group.setLayout(ets_layout) if CONF.get('main_interpreter','default'): interpreter = get_python_executable() else: interpreter = CONF.get('main_interpreter', 'executable') ets_group.setEnabled(programs.is_module_installed( "enthought.etsconfig.api", interpreter=interpreter)) tabs = QTabWidget() tabs.addTab(self.create_tab(interface_group, display_group, bg_group), _("Display")) tabs.addTab(self.create_tab(monitor_group, source_group), _("Introspection")) tabs.addTab(self.create_tab(pystartup_group), _("Advanced settings")) tabs.addTab(self.create_tab(qt_group, mpl_group, ets_group), _("External modules")) vlayout = QVBoxLayout() vlayout.addWidget(tabs) self.setLayout(vlayout)