Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
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()
Ejemplo n.º 3
0
    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].&lt;Tab&gt;</tt> or "
              "<tt>ins.meth().&lt;Tab&gt;</tt> <br>"
              "<b>Warning:</b> Due to a bug, IPython's "
              "greedy completer requires a leading "
              "<tt>&lt;Space&gt;</tt> for some completions; "
              "e.g.  <tt>np.sin(&lt;Space&gt;np.&lt;Tab&gt;"
              "</tt> works while <tt>np.sin(np.&lt;Tab&gt; "
              "</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 [&lt;span class="in-prompt-number"&gt;'
              '%i&lt;/span&gt;]:'),
            alignment=Qt.Horizontal)
        out_prompt_edit = self.create_lineedit(
            _("Output prompt:"),
            'out_prompt',
            '',
            _('Default is<br>'
              'Out[&lt;span class="out-prompt-number"&gt;'
              '%i&lt;/span&gt;]:'),
            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)
Ejemplo n.º 4
0
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()
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
    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].&lt;Tab&gt;</tt> or "
                                "<tt>ins.meth().&lt;Tab&gt;</tt> <br>"
                                "<b>Warning:</b> Due to a bug, IPython's "
                                "greedy completer requires a leading "
                                "<tt>&lt;Space&gt;</tt> for some completions; "
                                "e.g.  <tt>np.sin(&lt;Space&gt;np.&lt;Tab&gt;"
                                "</tt> works while <tt>np.sin(np.&lt;Tab&gt; "
                                "</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 [&lt;span class="in-prompt-number"&gt;'
              '%i&lt;/span&gt;]:'),
            alignment=Qt.Horizontal)
        out_prompt_edit = self.create_lineedit(
            _("Output prompt:"),
            'out_prompt', '',
            _('Default is<br>'
              'Out[&lt;span class="out-prompt-number"&gt;'
              '%i&lt;/span&gt;]:'),
            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)
Ejemplo n.º 8
0
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()
Ejemplo n.º 9
0
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()
Ejemplo n.º 10
0
    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)