Пример #1
0
class AcceptFiles(QDialog):
    def __init__(self, files):
        super().__init__()
        self.ok = QPushButton("Add", self)
        self.ok.clicked.connect(self.accept)
        discard = QPushButton("Discard", self)
        discard.clicked.connect(self.close)
        self.files = QListWidget(self)
        self.files.setSelectionMode(QAbstractItemView.ExtendedSelection)
        for file_name in files:
            self.files.addItem(file_name)
        for i in range(self.files.count()):
            self.files.item(i).setSelected(True)
        self.ok.setDefault(True)
        self.ok.setAutoDefault(True)

        layout = QVBoxLayout()
        layout.addWidget(QLabel("Found {} files".format(len(files))))
        layout.addWidget(self.files)
        butt_layout = QHBoxLayout()
        butt_layout.addWidget(discard)
        butt_layout.addStretch()
        butt_layout.addWidget(self.ok)
        layout.addLayout(butt_layout)
        self.setLayout(layout)

    def selection_changed(self):
        if self.files.selectedItems().count() == 0:
            self.ok.setDisabled(True)
        else:
            self.ok.setEnabled(True)

    def get_files(self):
        return [str(item.text()) for item in self.files.selectedItems()]
Пример #2
0
class DuplicateNormConfig(SiriusDialog):
    """Auxiliary window to duplicate a normalized config."""

    insertConfig = Signal(float, str, dict)

    def __init__(self, parent, psname2strength):
        """Initialize object."""
        super().__init__(parent)
        self.setObjectName('BOApp')
        self.setWindowTitle('Duplicate Normalized Configuration')
        self.psname2strength = psname2strength
        self._setupUi()

    def _setupUi(self):
        self.le_label = QLineEdit(self)
        self.sb_time = QDoubleSpinBoxPlus(self)
        self.sb_time.setMaximum(490)
        self.sb_time.setDecimals(3)
        self.bt_duplic = QPushButton('Duplicate', self)
        self.bt_duplic.setAutoDefault(False)
        self.bt_duplic.setDefault(False)
        self.bt_duplic.clicked.connect(self._emitConfigData)
        self.bt_cancel = QPushButton('Cancel', self)
        self.bt_cancel.setAutoDefault(False)
        self.bt_cancel.setDefault(False)
        self.bt_cancel.clicked.connect(self.close)

        # layout
        lay = QGridLayout()
        lay.setVerticalSpacing(15)
        lay.addWidget(QLabel('<h4>Duplicate Normalized Configuration</h4>',
                             self),
                      0,
                      0,
                      1,
                      2,
                      alignment=Qt.AlignCenter)
        lay.addWidget(
            QLabel(
                'Choose a label and a time to insert\n'
                'the new configuration:', self), 1, 0, 1, 2)
        lay.addWidget(QLabel('Label: ', self), 2, 0)
        lay.addWidget(self.le_label, 2, 1)
        lay.addWidget(QLabel('Time [ms]: ', self), 3, 0)
        lay.addWidget(self.sb_time, 3, 1)
        lay.addWidget(self.bt_cancel, 4, 0)
        lay.addWidget(self.bt_duplic, 4, 1)
        self.setLayout(lay)

    def _emitConfigData(self):
        time = self.sb_time.value()
        label = self.le_label.text()
        psname2strength = self.psname2strength
        self.insertConfig.emit(time, label, psname2strength)
        self.close()
Пример #3
0
    def __init__(self, parent):
        super(KiteIntegrationInfo, self).__init__(parent)
        # Images
        images_layout = QHBoxLayout()
        if is_dark_interface():
            icon_filename = 'spyder_kite.svg'
        else:
            icon_filename = 'spyder_kite_dark.svg'
        image_path = get_image_path(icon_filename)
        image = QPixmap(image_path)
        image_label = QLabel()
        screen = QApplication.primaryScreen()
        device_image_ratio = screen.devicePixelRatio()
        if device_image_ratio > 1:
            image.setDevicePixelRatio(device_image_ratio)
        else:
            image_height = image.height() * 0.5
            image_width = image.width() * 0.5
            image = image.scaled(image_width, image_height, Qt.KeepAspectRatio,
                                 Qt.SmoothTransformation)
        image_label.setPixmap(image)

        images_layout.addStretch()
        images_layout.addWidget(image_label)
        images_layout.addStretch()

        # Label
        integration_label = QLabel(
            _("Now Spyder can use <a href=\"{kite_url}\">Kite</a> to "
              "provide better and more accurate code completions in its "
              "editor <br>for the most important packages in the Python "
              "scientific ecosystem, such as Numpy, <br>Matplotlib and "
              "Pandas.<br><br>Would you like to install it or learn more "
              "about it?<br><br><i>Note:</i> Kite is free to use "
              "but is not an open source program.").format(
                  kite_url=KITE_SPYDER_URL))
        integration_label.setOpenExternalLinks(True)

        # Buttons
        buttons_layout = QHBoxLayout()
        learn_more_button = QPushButton(_('Learn more'))
        learn_more_button.setAutoDefault(False)
        install_button = QPushButton(_('Install Kite'))
        install_button.setAutoDefault(False)
        dismiss_button = QPushButton(_('Dismiss'))
        dismiss_button.setAutoDefault(False)
        buttons_layout.addStretch()
        buttons_layout.addWidget(install_button)
        buttons_layout.addWidget(learn_more_button)
        buttons_layout.addWidget(dismiss_button)

        general_layout = QVBoxLayout()
        general_layout.addLayout(images_layout)
        general_layout.addWidget(integration_label)
        general_layout.addLayout(buttons_layout)
        self.setLayout(general_layout)

        learn_more_button.clicked.connect(self.sig_learn_more_button_clicked)
        install_button.clicked.connect(self.sig_install_button_clicked)
        dismiss_button.clicked.connect(self.sig_dismiss_button_clicked)
Пример #4
0
    def __init__(self, parent=None):
        """
        """
        super(WriteQuitWidget, self).__init__(parent)
        self.parent = parent

        # Generate Buttons
        wbtn = QPushButton(self)
        wbtn.setText('Write')
        wbtn.setAutoDefault(False)
        wbtn.clicked.connect(self.parent.write_out)

        wqbtn = QPushButton(self)
        wqbtn.setText('Write\n Quit')
        wqbtn.setAutoDefault(False)
        wqbtn.clicked.connect(self.parent.write_quit)

        qbtn = QPushButton(self)
        qbtn.setText('Quit')
        qbtn.setAutoDefault(False)
        qbtn.clicked.connect(self.parent.quit)

        # Layout
        hbox = QHBoxLayout()
        hbox.addWidget(wbtn)
        hbox.addWidget(wqbtn)
        hbox.addWidget(qbtn)
        self.setLayout(hbox)
Пример #5
0
class TextEditor(QDialog):
    """Array Editor Dialog"""
    def __init__(self, text, title='', font=None, parent=None,
                 readonly=False, size=(400, 300)):
        QDialog.__init__(self, parent)
        
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        
        self.text = None
        self.btn_save_and_close = None
        
        # Display text as unicode if it comes as bytes, so users see 
        # its right representation
        if is_binary_string(text):
            self.is_binary = True
            text = to_text_string(text, 'utf8')
        else:
            self.is_binary = False
        
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        # Text edit
        self.edit = QTextEdit(parent)
        self.edit.setReadOnly(readonly)
        self.edit.textChanged.connect(self.text_changed)
        self.edit.setPlainText(text)
        if font is None:
            font = get_font()
        self.edit.setFont(font)
        self.layout.addWidget(self.edit)

        # Buttons configuration
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        if not readonly:
            self.btn_save_and_close = QPushButton(_('Save and Close'))
            self.btn_save_and_close.setDisabled(True)
            self.btn_save_and_close.clicked.connect(self.accept)
            btn_layout.addWidget(self.btn_save_and_close)

        self.btn_close = QPushButton(_('Close'))
        self.btn_close.setAutoDefault(True)
        self.btn_close.setDefault(True)
        self.btn_close.clicked.connect(self.reject)
        btn_layout.addWidget(self.btn_close)

        self.layout.addLayout(btn_layout)

        # Make the dialog act as a window
        self.setWindowFlags(Qt.Window)
        
        self.setWindowIcon(ima.icon('edit'))
        self.setWindowTitle(_("Text editor") + \
                            "%s" % (" - "+str(title) if str(title) else ""))
        self.resize(size[0], size[1])

    @Slot()
    def text_changed(self):
        """Text has changed"""
        # Save text as bytes, if it was initially bytes
        if self.is_binary:
            self.text = to_binary_string(self.edit.toPlainText(), 'utf8')
        else:
            self.text = to_text_string(self.edit.toPlainText())
        if self.btn_save_and_close:
            self.btn_save_and_close.setEnabled(True)
            self.btn_save_and_close.setAutoDefault(True)
            self.btn_save_and_close.setDefault(True)

    def get_value(self):
        """Return modified text"""
        # It is import to avoid accessing Qt C++ object as it has probably
        # already been destroyed, due to the Qt.WA_DeleteOnClose attribute
        return self.text

    def setup_and_check(self, value):
        """Verify if TextEditor is able to display strings passed to it."""
        try:
            to_text_string(value, 'utf8')
            return True
        except:
            return False
Пример #6
0
    def __init__(self, parent):
        super(KiteIntegrationInfo, self).__init__(parent)
        # Images
        images_layout = QHBoxLayout()
        icon_filename = 'kite_completions'
        image_path = get_image_path(icon_filename)
        image = QPixmap(image_path)
        image_label = QLabel()
        image_label = QLabel()
        image_height = int(image.height() * DialogStyle.IconScaleFactor)
        image_width = int(image.width() * DialogStyle.IconScaleFactor)
        image = image.scaled(image_width, image_height, Qt.KeepAspectRatio,
                             Qt.SmoothTransformation)
        image_label.setPixmap(image)

        images_layout.addStretch()
        images_layout.addWidget(image_label)
        images_layout.addStretch()

        ilayout = QHBoxLayout()
        ilayout.addLayout(images_layout)

        # Label
        integration_label_title = QLabel(
            "Get better code completions in Spyder")
        integration_label_title.setStyleSheet(
            f"font-size: {DialogStyle.TitleFontSize}")
        integration_label_title.setWordWrap(True)
        integration_label = QLabel(
            _("Now Spyder can use Kite to provide better code "
              "completions for key packages in the scientific Python "
              "Ecosystem. Install Kite for a better editor experience in "
              "Spyder. <br><br>Kite is free to use but is not open "
              "source. <a href=\"{kite_url}\">Learn more about Kite </a>").
            format(kite_url=KITE_SPYDER_URL))
        integration_label.setStyleSheet(
            f"font-size: {DialogStyle.ContentFontSize}")
        integration_label.setOpenExternalLinks(True)
        integration_label.setWordWrap(True)
        integration_label.setFixedWidth(360)
        label_layout = QVBoxLayout()
        label_layout.addWidget(integration_label_title)
        label_layout.addWidget(integration_label)

        # Buttons
        install_button_color = QStylePalette.COLOR_ACCENT_2
        install_button_hover = QStylePalette.COLOR_ACCENT_3
        install_button_pressed = QStylePalette.COLOR_ACCENT_4
        dismiss_button_color = QStylePalette.COLOR_BACKGROUND_4
        dismiss_button_hover = QStylePalette.COLOR_BACKGROUND_5
        dismiss_button_pressed = QStylePalette.COLOR_BACKGROUND_6
        font_color = QStylePalette.COLOR_TEXT_1
        buttons_layout = QHBoxLayout()
        install_button = QPushButton(_('Install Kite'))
        install_button.setAutoDefault(False)
        install_button.setStyleSheet(
            ("QPushButton {{ "
             "background-color: {background_color};"
             "border-color: {border_color};"
             "font-size: {font_size};"
             "color: {font_color};"
             "padding: {padding}}}"
             "QPushButton:hover:!pressed {{ "
             "background-color: {color_hover}}}"
             "QPushButton:pressed {{ "
             "background-color: {color_pressed}}}").format(
                 background_color=install_button_color,
                 border_color=install_button_color,
                 font_size=DialogStyle.ButtonsFontSize,
                 font_color=font_color,
                 padding=DialogStyle.ButtonsPadding,
                 color_hover=install_button_hover,
                 color_pressed=install_button_pressed))
        dismiss_button = QPushButton(_('Dismiss'))
        dismiss_button.setAutoDefault(False)
        dismiss_button.setStyleSheet(
            ("QPushButton {{ "
             "background-color: {background_color};"
             "border-color: {border_color};"
             "font-size: {font_size};"
             "color: {font_color};"
             "padding: {padding}}}"
             "QPushButton:hover:!pressed {{ "
             "background-color: {color_hover}}}"
             "QPushButton:pressed {{ "
             "background-color: {color_pressed}}}").format(
                 background_color=dismiss_button_color,
                 border_color=dismiss_button_color,
                 font_size=DialogStyle.ButtonsFontSize,
                 font_color=font_color,
                 padding=DialogStyle.ButtonsPadding,
                 color_hover=dismiss_button_hover,
                 color_pressed=dismiss_button_pressed))
        buttons_layout.addStretch()
        buttons_layout.addWidget(install_button)
        if not MAC:
            buttons_layout.addSpacing(10)
        buttons_layout.addWidget(dismiss_button)

        # Buttons with label
        vertical_layout = QVBoxLayout()
        if not MAC:
            vertical_layout.addStretch()
            vertical_layout.addLayout(label_layout)
            vertical_layout.addSpacing(20)
            vertical_layout.addLayout(buttons_layout)
            vertical_layout.addStretch()
        else:
            vertical_layout.addLayout(label_layout)
            vertical_layout.addLayout(buttons_layout)

        general_layout = QHBoxLayout()
        general_layout.addStretch()
        general_layout.addLayout(ilayout)
        general_layout.addSpacing(15)
        general_layout.addLayout(vertical_layout)
        general_layout.addStretch()

        self.setLayout(general_layout)

        # Signals
        install_button.clicked.connect(self.sig_install_button_clicked)
        dismiss_button.clicked.connect(self.sig_dismiss_button_clicked)

        self.setStyleSheet(
            f"background-color: {QStylePalette.COLOR_BACKGROUND_2}")
        self.setContentsMargins(18, 40, 18, 40)
        if not MAC:
            self.setFixedSize(800, 350)
Пример #7
0
class CondaPackagesWidget(QWidget):
    """
    Conda Packages Widget.
    """

    # Location of updated repo.json files from continuum/binstar
    CONDA_CONF_PATH = get_conf_path('repo')

    # Location of continuum/anaconda default repos shipped with conda-manager
    DATA_PATH = get_module_data_path()

    # file inside DATA_PATH with metadata for conda packages
    DATABASE_FILE = 'packages.ini'

    sig_worker_ready = Signal()
    sig_packages_ready = Signal()
    sig_environment_created = Signal(object, object)
    sig_environment_removed = Signal(object, object)
    sig_environment_cloned = Signal(object, object)
    sig_channels_updated = Signal(tuple, tuple)  # channels, active_channels
    sig_process_cancelled = Signal()
    sig_next_focus = Signal()
    sig_packages_busy = Signal()

    def __init__(self,
                 parent,
                 name=None,
                 prefix=None,
                 channels=(),
                 active_channels=(),
                 conda_url='https://conda.anaconda.org',
                 conda_api_url='https://api.anaconda.org',
                 setup=True,
                 data_directory=None,
                 extra_metadata={}):

        super(CondaPackagesWidget, self).__init__(parent)

        # Check arguments: active channels, must be witbhin channels
        for ch in active_channels:
            if ch not in channels:
                raise Exception("'active_channels' must be also within "
                                "'channels'")

        if data_directory is None:
            data_directory = self.CONDA_CONF_PATH

        self._parent = parent
        self._current_action_name = ''
        self._hide_widgets = False
        self._metadata = extra_metadata  # From repo.continuum
        self._metadata_links = {}        # Bundled metadata
        self.api = ManagerAPI()
        self.busy = False
        self.data_directory = data_directory
        self.conda_url = conda_url
        self.conda_api_url = conda_api_url
        self.name = name
        self.package_blacklist = []
        self.prefix = prefix
        self.root_prefix = self.api.ROOT_PREFIX
        self.style_sheet = None
        self.message = ''
        self.apply_actions_dialog = None
        self.conda_errors = []
        self.message_box_error = None
        self.token = None

        if channels:
            self._channels = channels
            self._active_channels = active_channels
        else:
            self._channels = self.api.conda_get_condarc_channels()
            self._active_channels = self._channels[:]

        try:
            import spyderlib.utils.icon_manager as ima
            icon_options = ima.icon('tooloptions')
        except Exception:
            import qtawesome as qta
            icon_options = qta.icon('fa.cog')

        # Widgets
        self.cancel_dialog = ClosePackageManagerDialog
        self.bbox = QDialogButtonBox(Qt.Horizontal)
        self.button_cancel = QPushButton('Cancel')
        self.button_channels = QPushButton(_('Channels'))
        self.button_ok = QPushButton(_('Ok'))
        self.button_update = QPushButton(_('Update index...'))
        self.button_apply = QPushButton(_('Apply'))
        self.button_clear = QPushButton(_('Clear'))
        self.button_options = QToolButton()
        self.combobox_filter = DropdownPackageFilter(self)
        self.frame_top = FramePackageTop()
        self.frame_bottom = FramePackageTop()
        self.progress_bar = ProgressBarPackage(self)
        self.status_bar = LabelPackageStatus(self)
        self.table = TableCondaPackages(self)
        self.textbox_search = LineEditSearch(self)
        self.widgets = [self.button_update, self.button_channels,
                        self.combobox_filter, self.textbox_search, self.table,
                        self.button_ok, self.button_apply, self.button_clear,
                        self.button_options]
        self.table_first_row = FirstRowWidget(
            widget_before=self.textbox_search)
        self.table_last_row = LastRowWidget(
            widgets_after=[self.button_apply, self.button_clear,
                           self.button_cancel, self.combobox_filter])

        # Widget setup
        self.button_options.setPopupMode(QToolButton.InstantPopup)
        self.button_options.setIcon(icon_options)
        self.button_options.setAutoRaise(True)

        max_height = self.status_bar.fontMetrics().height()
        max_width = self.textbox_search.fontMetrics().width('M'*23)
        self.bbox.addButton(self.button_ok, QDialogButtonBox.ActionRole)
        self.button_ok.setAutoDefault(True)
        self.button_ok.setDefault(True)
        self.button_ok.setMaximumSize(QSize(0, 0))
        self.button_ok.setVisible(False)
        self.combobox_filter.addItems([k for k in C.COMBOBOX_VALUES_ORDERED])
        self.combobox_filter.setMinimumWidth(120)
        self.progress_bar.setMaximumHeight(max_height*1.2)
        self.progress_bar.setMaximumWidth(max_height*12)
        self.progress_bar.setTextVisible(False)
        self.progress_bar.setVisible(False)
        self.setMinimumSize(QSize(480, 300))
        self.setWindowTitle(_("Conda Package Manager"))
        self.status_bar.setFixedHeight(max_height*1.5)
        self.textbox_search.setMaximumWidth(max_width)
        self.textbox_search.setPlaceholderText('Search Packages')
        self.table_first_row.setMaximumHeight(0)
        self.table_last_row.setMaximumHeight(0)
        self.table_last_row.setVisible(False)
        self.table_first_row.setVisible(False)

        # Layout
        top_layout = QHBoxLayout()
        top_layout.addWidget(self.combobox_filter)
        top_layout.addWidget(self.button_channels)
        top_layout.addWidget(self.button_update)
        top_layout.addWidget(self.textbox_search)
        top_layout.addStretch()
        top_layout.addWidget(self.button_options)

        middle_layout = QVBoxLayout()
        middle_layout.addWidget(self.table_first_row)
        middle_layout.addWidget(self.table)
        middle_layout.addWidget(self.table_last_row)

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(self.status_bar)
        bottom_layout.addStretch()
        bottom_layout.addWidget(self.progress_bar)
        bottom_layout.addWidget(self.button_cancel)
        bottom_layout.addWidget(self.button_apply)
        bottom_layout.addWidget(self.button_clear)

        layout = QVBoxLayout(self)
        layout.addLayout(top_layout)
        layout.addLayout(middle_layout)
        layout.addLayout(bottom_layout)
        layout.addSpacing(6)
        self.setLayout(layout)

        self.setTabOrder(self.combobox_filter, self.button_channels)
        self.setTabOrder(self.button_channels, self.button_update)
        self.setTabOrder(self.button_update, self.textbox_search)
        self.setTabOrder(self.textbox_search, self.table_first_row)
        self.setTabOrder(self.table, self.table_last_row)
        self.setTabOrder(self.table_last_row, self.button_apply)
        self.setTabOrder(self.button_apply, self.button_clear)
        self.setTabOrder(self.button_clear, self.button_cancel)

        # Signals and slots
        self.api.sig_repodata_updated.connect(self._repodata_updated)
        self.combobox_filter.currentIndexChanged.connect(self.filter_package)
        self.button_apply.clicked.connect(self.apply_multiple_actions)
        self.button_clear.clicked.connect(self.clear_actions)
        self.button_cancel.clicked.connect(self.cancel_process)
        self.button_channels.clicked.connect(self.show_channels_dialog)
        self.button_update.clicked.connect(self.update_package_index)
        self.textbox_search.textChanged.connect(self.search_package)
        self.table.sig_conda_action_requested.connect(self._run_conda_action)
        self.table.sig_actions_updated.connect(self.update_actions)
        self.table.sig_pip_action_requested.connect(self._run_pip_action)
        self.table.sig_status_updated.connect(self.update_status)
        self.table.sig_next_focus.connect(self.table_last_row.handle_tab)
        self.table.sig_previous_focus.connect(
            lambda: self.table_first_row.widget_before.setFocus())
        self.table_first_row.sig_enter_first.connect(self._handle_tab_focus)
        self.table_last_row.sig_enter_last.connect(self._handle_backtab_focus)

        # Setup
        self.api.client_set_domain(conda_api_url)
        self.api.set_data_directory(self.data_directory)
        self._load_bundled_metadata()
        self.update_actions(0)

        if setup:
            self.set_environment(name=name, prefix=prefix)
            self.setup()

    # --- Helpers
    # -------------------------------------------------------------------------
    def _handle_tab_focus(self):
        self.table.setFocus()
        if self.table.proxy_model:
            index = self.table.proxy_model.index(0, 0)
            self.table.setCurrentIndex(index)

    def _handle_backtab_focus(self):
        self.table.setFocus()
        if self.table.proxy_model:
            row = self.table.proxy_model.rowCount() - 1
            index = self.table.proxy_model.index(row, 0)
            self.table.setCurrentIndex(index)

    # --- Callbacks
    # -------------------------------------------------------------------------
    def _load_bundled_metadata(self):
        """
        """
        logger.debug('')

        parser = cp.ConfigParser()
        db_file = CondaPackagesWidget.DATABASE_FILE
        with open(osp.join(self.DATA_PATH, db_file)) as f:
            parser.readfp(f)

        for name in parser.sections():
            metadata = {}
            for key, data in parser.items(name):
                metadata[key] = data
            self._metadata_links[name] = metadata

    def _setup_packages(self, worker, data, error):
        """
        """
        if error:
            logger.error(error)
        else:
            logger.debug('')

        combobox_index = self.combobox_filter.currentIndex()
        status = C.PACKAGE_STATUS[combobox_index]

        packages = worker.packages

        # Remove blacklisted packages
        for package in self.package_blacklist:
            if package in packages:
                packages.pop(package)
            for i, row in enumerate(data):
                if package == data[i][C.COL_NAME]:
                    data.pop(i)

        self.table.setup_model(packages, data, self._metadata_links)
        self.combobox_filter.setCurrentIndex(combobox_index)
        self.filter_package(status)

        if self._current_model_index:
            self.table.setCurrentIndex(self._current_model_index)
            self.table.verticalScrollBar().setValue(self._current_table_scroll)

        if error:
            self.update_status(error, False)
        self.sig_packages_ready.emit()
        self.table.setFocus()

    def get_logged_user_list_channels(self):
        channels = []
        for ch in self._active_channels:
            if self.conda_url in ch and 'repo.continuum' not in ch:
                channel = ch.split('/')[-1]
                channels.append(channel)
        return channels

    def _prepare_model_data(self, worker=None, output=None, error=None):
        """
        """
        if error:
            logger.error(error)
        else:
            logger.debug('')

        packages, apps = output
#        worker = self.api.pip_list(prefix=self.prefix)
#        worker.sig_finished.connect(self._pip_list_ready)
        logins = self.get_logged_user_list_channels()
        worker = self.api.client_multi_packages(logins=logins,
                                                access='private')
        worker.sig_finished.connect(self._user_private_packages_ready)
        worker.packages = packages
        worker.apps = apps

    def _user_private_packages_ready(self, worker, output, error):
        if error:
            logger.error(error)
        else:
            logger.debug('')

        packages = worker.packages
        apps = worker.apps
        worker = self.api.pip_list(prefix=self.prefix)
        worker.sig_finished.connect(self._pip_list_ready)
        worker.packages = packages
        worker.apps = apps

#        private_packages = {}
#        if output:
#            all_private_packages = output
#            for item in all_private_packages:
#                name = item.get('name', '')
#                public = item.get('public', True)
#                package_types = item.get('package_types', [])
#                latest_version = item.get('latest_version', '')
#                if name and not public and 'conda' in package_types:
#                    private_packages[name] = {'versions': item.get('versions', []),
#                                              'app_entry': {},
#                                              'type': {},
#                                              'size': {},
#                                              'latest_version': latest_version,
#                                              }
        worker.private_packages = output

    def _pip_list_ready(self, worker, pip_packages, error):
        """
        """
        if error:
            logger.error(error)
        else:
            logger.debug('')

        packages = worker.packages
        private_packages = worker.private_packages
        linked_packages = self.api.conda_linked(prefix=self.prefix)
        data = self.api.client_prepare_packages_data(packages,
                                                     linked_packages,
                                                     pip_packages,
                                                     private_packages)

        combobox_index = self.combobox_filter.currentIndex()
        status = C.PACKAGE_STATUS[combobox_index]

        # Remove blacklisted packages
        for package in self.package_blacklist:
            if package in packages:
                packages.pop(package)

            for i, row in enumerate(data):
                if package == data[i][C.COL_NAME]:
                    data.pop(i)

        self.table.setup_model(packages, data, self._metadata_links)
        self.combobox_filter.setCurrentIndex(combobox_index)
        self.filter_package(status)

        if self._current_model_index:
            self.table.setCurrentIndex(self._current_model_index)
            self.table.verticalScrollBar().setValue(self._current_table_scroll)

        if error:
            self.update_status(str(error), False)
        self.sig_packages_ready.emit()
        self.table.setFocus()

    def _repodata_updated(self, paths):
        """
        """
        worker = self.api.client_load_repodata(paths, extra_data={},
                                               metadata=self._metadata)
        worker.paths = paths
        worker.sig_finished.connect(self._prepare_model_data)

    def _metadata_updated(self, worker, path, error):
        """
        """
        if error:
            logger.error(error)
        else:
            logger.debug('')

        if path and osp.isfile(path):
            with open(path, 'r') as f:
                data = f.read()
            try:
                self._metadata = json.loads(data)
            except Exception:
                self._metadata = {}
        else:
            self._metadata = {}
        self.api.update_repodata(self._channels)

    # ---
    # -------------------------------------------------------------------------
    def _run_multiple_actions(self, worker=None, output=None, error=None):
        """
        """
        logger.error(str(error))

        if output and isinstance(output, dict):
            conda_error_type = output.get('error_type', None)
            conda_error = output.get('error', None)

            if conda_error_type or conda_error:
                self.conda_errors.append((conda_error_type, conda_error))
                logger.error((conda_error_type, conda_error))

        if self._multiple_process:
            status, func = self._multiple_process.popleft()
            self.update_status(status)
            worker = func()
            worker.sig_finished.connect(self._run_multiple_actions)
            worker.sig_partial.connect(self._partial_output_ready)
        else:
            if self.conda_errors and self.message_box_error:
                text = "The following errors occured:"
                error = ''
                for conda_error in self.conda_errors:
                    error += str(conda_error[0]) + ':\n'
                    error += str(conda_error[1]) + '\n\n'
                dlg = self.message_box_error(text=text, error=error,
                                             title='Conda process error')
                dlg.setMinimumWidth(400)
                dlg.exec_()

            self.update_status('', hide=False)
            self.setup()

    def _pip_process_ready(self, worker, output, error):
        """
        """
        if error is not None:
            status = _('there was an error')
            self.update_status(hide=False, message=status)
        else:
            self.update_status(hide=True)

        self.setup()

    def _conda_process_ready(self, worker, output, error):
        """
        """
        if error is not None:
            status = _('there was an error')
            self.update_status(hide=False, message=status)
        else:
            self.update_status(hide=True)

        conda_error = None
        conda_error_type = None
        if output and isinstance(output, dict):
            conda_error_type = output.get('error_type')
            conda_error = output.get('error')

            if conda_error_type or conda_error:
                logger.error((conda_error_type, conda_error))

        dic = self._temporal_action_dic

        if dic['action'] == C.ACTION_CREATE:
            self.sig_environment_created.emit(conda_error, conda_error_type)
        elif dic['action'] == C.ACTION_CLONE:
            self.sig_environment_cloned.emit(conda_error, conda_error_type)
        elif dic['action'] == C.ACTION_REMOVE_ENV:
            self.sig_environment_removed.emit(conda_error, conda_error_type)

        self.setup()

    def _partial_output_ready(self, worker, output, error):
        """
        """
        message = None
        progress = (0, 0)

        if isinstance(output, dict):
            progress = (output.get('progress', None),
                        output.get('maxval', None))
            name = output.get('name', None)
            fetch = output.get('fetch', None)

            if fetch:
                message = "Downloading <b>{0}</b>...".format(fetch)

            if name:
                self._current_action_name = name
                message = "Linking <b>{0}</b>...".format(name)

        logger.debug(message)
        self.update_status(message, progress=progress)

    def _run_pip_action(self, package_name, action):
        """
        DEPRECATED
        """
        prefix = self.prefix

        if prefix == self.root_prefix:
            name = 'root'
        elif self.api.conda_environment_exists(prefix=prefix):
            name = osp.basename(prefix)
        else:
            name = prefix

        if action == C.ACTION_REMOVE:
            msgbox = QMessageBox.question(self,
                                          "Remove pip package: "
                                          "{0}".format(package_name),
                                          "Do you want to proceed?",
                                          QMessageBox.Yes | QMessageBox.No)
            if msgbox == QMessageBox.Yes:
                self.update_status()
                worker = self.api.pip_remove(prefix=self.prefix,
                                             pkgs=[package_name])
                worker.sig_finished.connect(self._pip_process_ready)
                status = (_('Removing pip package <b>') + package_name +
                          '</b>' + _(' from <i>') + name + '</i>')
                self.update_status(hide=True, message=status,
                                   progress=[0, 0])

    def _run_conda_action(self, package_name, action, version, versions,
                          packages_sizes):
        """
        DEPRECATED
        """
        prefix = self.prefix
        dlg = CondaPackageActionDialog(self, prefix, package_name, action,
                                       version, versions, packages_sizes,
                                       self._active_channels)

        if dlg.exec_():
            dic = {}

            self.status = 'Processing'
            self.update_status(hide=True)
            self.repaint()

            ver1 = dlg.label_version.text()
            ver2 = dlg.combobox_version.currentText()
            pkg = u'{0}={1}{2}'.format(package_name, ver1, ver2)
            dep = dlg.checkbox.checkState()
            state = dlg.checkbox.isEnabled()
            dlg.close()

            dic['pkg'] = pkg
            dic['dep'] = not (dep == 0 and state)
            dic['action'] = None
            self._run_conda_process(action, dic)

    def _run_conda_process(self, action, dic):
        """
        DEPRECTAED
        """
        self._temporal_action_dic = dic
#        prefix = self.prefix
#
#        if prefix == self.root_prefix:
#            name = 'root'
#        elif self.api.conda_environment_exists(prefix=prefix):
#            name = osp.basename(prefix)
#        else:
#            name = prefix

        if 'pkgs' in dic and 'dep' in dic:
            dep = dic['dep']
            pkgs = dic['pkgs']
            if not isinstance(pkgs, list):
                pkgs = [pkgs]

#        if (action == C.ACTION_INSTALL or action == C.ACTION_UPGRADE or
#           action == C.ACTION_DOWNGRADE):
#            status = _('Installing <b>') + dic['pkg'] + '</b>'
#            status = status + _(' into <i>') + name + '</i>'
#            worker = self.api.conda_install(prefix=prefix, pkgs=pkgs, dep=dep,
#                                            channels=self._active_channels)
#        elif action == C.ACTION_REMOVE:
#            status = (_('Removing <b>') + dic['pkg'] + '</b>' +
#                      _(' from <i>') + name + '</i>')
#            worker = self.api.conda_remove(pkgs[0], prefix=prefix)

        # --- Environment management actions
        name = dic['name']
        if action == C.ACTION_CREATE:
            status = _('Creating environment <b>') + name + '</b>'
            worker = self.api.conda_create(name=name, pkgs=pkgs,
                                           channels=self._active_channels)
        elif action == C.ACTION_CLONE:
            clone = dic['clone']
            status = (_('Cloning ') + '<i>' + clone +
                      _('</i> into <b>') + name + '</b>')
            worker = self.api.conda_clone(clone, name=name)
        elif action == C.ACTION_REMOVE_ENV:
            status = _('Removing environment <b>') + name + '</b>'
            worker = self.api.conda_remove(name=name, all_=True)

        worker.sig_finished.connect(self._conda_process_ready)
        worker.sig_partial.connect(self._partial_output_ready)
        self.update_status(hide=True, message=status, progress=None)
        self._temporal_action_dic = dic
        return worker

    # Public API
    # -------------------------------------------------------------------------
    def prepare_model_data(self, packages, apps):
        """
        """
        logger.debug('')
        self._prepare_model_data(output=(packages, apps))

    # These should be private methods....
    def enable_widgets(self):
        """ """
        self.table.hide_columns()

    def disable_widgets(self):
        """ """
        self.table.hide_action_columns()

    def accept_channels_dialog(self):
        self.button_channels.setFocus()
        self.button_channels.toggle()

    def update_actions(self, number_of_actions):
        """
        """
        self.button_apply.setVisible(bool(number_of_actions))
        self.button_clear.setVisible(bool(number_of_actions))

    # --- Non UI API
    # -------------------------------------------------------------------------
    def setup(self, check_updates=False, blacklist=[], metadata={}):
        """
        Setup packages.

        Main triger method to download repodata, load repodata, prepare and
        updating the data model.

        Parameters
        ----------
        check_updates : bool
            If `True`, checks that the latest repodata is available on the
            listed channels. If `False`, the data will be loaded from the
            downloaded files without checking for newer versions.
        blacklist: list of str
            List of conda package names to be excluded from the actual package
            manager view.
        """
        self.sig_packages_busy.emit()

        if self.busy:
            logger.debug('Busy...')
            return
        else:
            logger.debug('')

        if blacklist:
            self.package_blacklist = [p.lower() for p in blacklist]

        if metadata:
            self._metadata = metadata

        self._current_model_index = self.table.currentIndex()
        self._current_table_scroll = self.table.verticalScrollBar().value()
        self.update_status('Updating package index', True)

        if check_updates:
            worker = self.api.update_metadata()
            worker.sig_finished.connect(self._metadata_updated)
        else:
            paths = self.api.repodata_files(channels=self._active_channels)
            self._repodata_updated(paths)

    def update_domains(self, anaconda_api_url=None, conda_url=None):
        """
        """
        logger.debug(str((anaconda_api_url, conda_url)))
        update = False

        if anaconda_api_url:
            if self.conda_api_url != anaconda_api_url:
                update = True

            self.conda_api_url = anaconda_api_url
            self.api.client_set_domain(anaconda_api_url)

        if conda_url:
            if self.conda_url != conda_url:
                update = True
            self.conda_url = conda_url

        if update:
            pass

    def set_environment(self, name=None, prefix=None):
        """
        This does not update the package manager!
        """
        logger.debug(str((name, prefix)))

        if prefix and self.api.conda_environment_exists(prefix=prefix):
            self.prefix = prefix
        elif name and self.api.conda_environment_exists(name=name):
            self.prefix = self.get_prefix_envname(name)
        else:
            self.prefix = self.root_prefix

    def set_token(self, token):
        self.token = token

    def update_channels(self, channels, active_channels):
        """
        """
        logger.debug(str((channels, active_channels)))

        if sorted(self._active_channels) != sorted(active_channels) or \
                sorted(self._channels) != sorted(channels):
            self._channels = channels
            self._active_channels = active_channels
            self.sig_channels_updated.emit(tuple(channels),
                                           tuple(active_channels))
            self.setup(check_updates=True)

    def update_style_sheet(self, style_sheet=None, extra_dialogs={},
                           palette={}):
        if style_sheet:
            self.style_sheet = style_sheet
            self.table.update_style_palette(palette=palette)
            self.textbox_search.update_style_sheet(style_sheet)
            self.setStyleSheet(style_sheet)

        if extra_dialogs:
            cancel_dialog = extra_dialogs.get('cancel_dialog', None)
            apply_actions_dialog = extra_dialogs.get('apply_actions_dialog',
                                                     None)
            message_box_error = extra_dialogs.get('message_box_error',
                                                  None)
            if cancel_dialog:
                self.cancel_dialog = cancel_dialog
            if apply_actions_dialog:
                self.apply_actions_dialog = apply_actions_dialog
            if message_box_error:
                self.message_box_error = message_box_error

    # --- UI API
    # -------------------------------------------------------------------------
    def filter_package(self, value):
        """ """
        self.table.filter_status_changed(value)

    def show_channels_dialog(self):
        """
        Show the channels dialog.
        """
        button_channels = self.button_channels
        self.dlg = DialogChannels(self,
                                  channels=self._channels,
                                  active_channels=self._active_channels,
                                  conda_url=self.conda_url)
        self.dlg.update_style_sheet(style_sheet=self.style_sheet)
        button_channels.setDisabled(True)
        self.dlg.sig_channels_updated.connect(self.update_channels)
        self.dlg.rejected.connect(lambda: button_channels.setEnabled(True))
        self.dlg.rejected.connect(button_channels.toggle)
        self.dlg.rejected.connect(button_channels.setFocus)
        self.dlg.accepted.connect(self.accept_channels_dialog)

        geo_tl = button_channels.geometry().topLeft()
        tl = button_channels.parentWidget().mapToGlobal(geo_tl)
        x = tl.x() + 2
        y = tl.y() + button_channels.height()
        self.dlg.move(x, y)
        self.dlg.show()
        self.dlg.button_add.setFocus()

    def update_package_index(self):
        """ """
        self.setup(check_updates=True)

    def search_package(self, text):
        """ """
        self.table.search_string_changed(text)

    def apply_multiple_actions(self):
        """
        """
        logger.debug('')

        self.conda_errors = []

        prefix = self.prefix

        if prefix == self.root_prefix:
            name = 'root'
        elif self.api.conda_environment_exists(prefix=prefix):
            name = osp.basename(prefix)
        else:
            name = prefix

        actions = self.table.get_actions()

        if actions is None:
            return

        self._multiple_process = deque()

        pip_actions = actions[C.PIP_PACKAGE]
        conda_actions = actions[C.CONDA_PACKAGE]

        pip_remove = pip_actions.get(C.ACTION_REMOVE, [])
        conda_remove = conda_actions.get(C.ACTION_REMOVE, [])
        conda_install = conda_actions.get(C.ACTION_INSTALL, [])
        conda_upgrade = conda_actions.get(C.ACTION_UPGRADE, [])
        conda_downgrade = conda_actions.get(C.ACTION_DOWNGRADE, [])

        message = ''
        template_1 = '<li><b>{0}={1}</b></li>'
        template_2 = '<li><b>{0}: {1} -> {2}</b></li>'

        if pip_remove:
            temp = [template_1.format(i['name'], i['version_to']) for i in
                    pip_remove]
            message += ('The following pip packages will be removed: '
                        '<ul>' + ''.join(temp) + '</ul>')
        if conda_remove:
            temp = [template_1.format(i['name'], i['version_to']) for i in
                    conda_remove]
            message += ('<br>The following conda packages will be removed: '
                        '<ul>' + ''.join(temp) + '</ul>')
        if conda_install:
            temp = [template_1.format(i['name'], i['version_to']) for i in
                    conda_install]
            message += ('<br>The following conda packages will be installed: '
                        '<ul>' + ''.join(temp) + '</ul>')
        if conda_downgrade:
            temp = [template_2.format(
                    i['name'], i['version_from'], i['version_to']) for i in
                    conda_downgrade]
            message += ('<br>The following conda packages will be downgraded: '
                        '<ul>' + ''.join(temp) + '</ul>')
        if conda_upgrade:
            temp = [template_2.format(
                    i['name'], i['version_from'], i['version_to']) for i in
                    conda_upgrade]
            message += ('<br>The following conda packages will be upgraded: '
                        '<ul>' + ''.join(temp) + '</ul>')
        message += '<br>'

        if self.apply_actions_dialog:
            dlg = self.apply_actions_dialog(message, parent=self)
            dlg.update_style_sheet(style_sheet=self.style_sheet)
            reply = dlg.exec_()
        else:
            reply = QMessageBox.question(self,
                                         'Proceed with the following actions?',
                                         message,
                                         buttons=QMessageBox.Ok |
                                         QMessageBox.Cancel)

        if reply:
            # Pip remove
            for pkg in pip_remove:
                status = (_('Removing pip package <b>') + pkg['name'] +
                          '</b>' + _(' from <i>') + name + '</i>')
                pkgs = [pkg['name']]

                def trigger(prefix=prefix, pkgs=pkgs):
                    return lambda: self.api.pip_remove(prefix=prefix,
                                                       pkgs=pkgs)

                self._multiple_process.append([status, trigger()])

            # Process conda actions
            if conda_remove:
                status = (_('Removing conda packages <b>') +
                          '</b>' + _(' from <i>') + name + '</i>')
                pkgs = [i['name'] for i in conda_remove]

                def trigger(prefix=prefix, pkgs=pkgs):
                    return lambda: self.api.conda_remove(pkgs=pkgs,
                                                         prefix=prefix)
                self._multiple_process.append([status, trigger()])

            if conda_install:
                pkgs = ['{0}={1}'.format(i['name'], i['version_to']) for i in
                        conda_install]

                status = (_('Installing conda packages <b>') +
                          '</b>' + _(' on <i>') + name + '</i>')

                def trigger(prefix=prefix, pkgs=pkgs):
                    return lambda: self.api.conda_install(
                        prefix=prefix,
                        pkgs=pkgs,
                        channels=self._active_channels,
                        token=self.token)
                self._multiple_process.append([status, trigger()])

            # Conda downgrade
            if conda_downgrade:
                status = (_('Downgrading conda packages <b>') +
                          '</b>' + _(' on <i>') + name + '</i>')

                pkgs = ['{0}={1}'.format(i['name'], i['version_to']) for i in
                        conda_downgrade]

                def trigger(prefix=prefix, pkgs=pkgs):
                    return lambda: self.api.conda_install(
                        prefix=prefix,
                        pkgs=pkgs,
                        channels=self._active_channels,
                        token=self.token)

                self._multiple_process.append([status, trigger()])

            # Conda update
            if conda_upgrade:
                status = (_('Upgrading conda packages <b>') +
                          '</b>' + _(' on <i>') + name + '</i>')

                pkgs = ['{0}={1}'.format(i['name'], i['version_to']) for i in
                        conda_upgrade]

                def trigger(prefix=prefix, pkgs=pkgs):
                    return lambda: self.api.conda_install(
                        prefix=prefix,
                        pkgs=pkgs,
                        channels=self._active_channels,
                        token=self.token)

                self._multiple_process.append([status, trigger()])

            self._run_multiple_actions()

    def clear_actions(self):
        """
        """
        self.table.clear_actions()

    def cancel_process(self):
        """
        Allow user to cancel an ongoing process.
        """
        logger.debug(str('process canceled by user.'))
        if self.busy:
            dlg = self.cancel_dialog()
            reply = dlg.exec_()
            if reply:
                self.update_status(hide=False, message='Process cancelled')
                self.api.conda_terminate()
                self.api.download_requests_terminate()
                self.api.conda_clear_lock()
                self.table.clear_actions()
                self.sig_process_cancelled.emit()
        else:
            QDialog.reject(self)

    def update_status(self, message=None, hide=True, progress=None,
                      env=False):
        """
        Update status bar, progress bar display and widget visibility

        message : str
            Message to display in status bar.
        hide : bool
            Enable/Disable widgets.
        progress : [int, int]
            Show status bar progress. [0, 0] means spinning statusbar.
        """
        self.busy = hide
        for widget in self.widgets:
            widget.setDisabled(hide)
        self.table.verticalScrollBar().setValue(self._current_table_scroll)

        self.button_apply.setVisible(False)
        self.button_clear.setVisible(False)

        self.progress_bar.setVisible(hide)
        self.button_cancel.setVisible(hide)

        if message is not None:
            self.message = message

        if self.prefix == self.root_prefix:
            short_env = 'root'
#        elif self.api.environment_exists(prefix=self.prefix):
#            short_env = osp.basename(self.prefix)
        else:
            short_env = self.prefix

        if env:
            self.message = '{0} (<b>{1}</b>)'.format(
                self.message, short_env,
                )
        self.status_bar.setText(self.message)

        if progress is not None:
            current_progress, max_progress = 0, 0

            if progress[1]:
                max_progress = progress[1]

            if progress[0]:
                current_progress = progress[0]

            self.progress_bar.setMinimum(0)
            self.progress_bar.setMaximum(max_progress)
            self.progress_bar.setValue(current_progress)
        else:
            self.progress_bar.setMinimum(0)
            self.progress_bar.setMaximum(0)

    # --- Conda helpers
    # -------------------------------------------------------------------------
    def get_environment_prefix(self):
        """
        Returns the active environment prefix.
        """
        return self.prefix

    def get_environment_name(self):
        """
        Returns the active environment name if it is located in the default
        conda environments directory, otherwise it returns the prefix.
        """
        name = osp.basename(self.prefix)

        if not (name and self.api.environment_exists(name=name)):
            name = self.prefix

        return name

    def get_environments(self):
        """
        Get a list of conda environments located in the default conda
        environments directory.
        """
        return self.api.conda_get_envs()

    def get_prefix_envname(self, name):
        """
        Returns the prefix for a given environment by name.
        """
        return self.api.conda_get_prefix_envname(name)

    def get_package_versions(self, name):
        """ """
        return self.table.source_model.get_package_versions(name)

    # --- Conda actions
    # -------------------------------------------------------------------------
    def create_environment(self, name=None, prefix=None, packages=['python']):
        """ """
        # If environment exists already? GUI should take care of this
        # BUT the api call should simply set that env as the env
        dic = {}
        dic['name'] = name
        dic['prefix'] = prefix
        dic['pkgs'] = packages
        dic['dep'] = True  # Not really needed but for the moment!
        dic['action'] = C.ACTION_CREATE
        return self._run_conda_process(dic['action'], dic)

    def clone_environment(self, name=None, prefix=None, clone=None):
        dic = {}
        dic['name'] = name
        dic['prefix'] = prefix
        dic['clone'] = clone
        dic['pkgs'] = None
        dic['dep'] = True  # Not really needed but for the moment!
        dic['action'] = C.ACTION_CLONE
        return self._run_conda_process(dic['action'], dic)

    def remove_environment(self, name=None, prefix=None):
        dic = {}
        dic['name'] = name
        dic['pkgs'] = None
        dic['dep'] = True  # Not really needed but for the moment!
        dic['action'] = C.ACTION_REMOVE_ENV
        return self._run_conda_process(dic['action'], dic)

    # New api
    def show_login_dialog(self):
        pass

    def show_options_menu(self):
        pass
Пример #8
0
class ArrayEditor(QDialog):
    """Array Editor Dialog"""    
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        
        self.data = None
        self.arraywidget = None
        self.stack = None
        self.layout = None
        self.btn_save_and_close = None
        self.btn_close = None
        # Values for 3d array editor
        self.dim_indexes = [{}, {}, {}]
        self.last_dim = 0  # Adjust this for changing the startup dimension
        
    def setup_and_check(self, data, title='', readonly=False,
                        xlabels=None, ylabels=None):
        """
        Setup ArrayEditor:
        return False if data is not supported, True otherwise
        """
        self.data = data
        readonly = readonly or not self.data.flags.writeable
        is_record_array = data.dtype.names is not None
        is_masked_array = isinstance(data, np.ma.MaskedArray)

        if data.ndim > 3:
            self.error(_("Arrays with more than 3 dimensions are not "
                         "supported"))
            return False
        if xlabels is not None and len(xlabels) != self.data.shape[1]:
            self.error(_("The 'xlabels' argument length do no match array "
                         "column number"))
            return False
        if ylabels is not None and len(ylabels) != self.data.shape[0]:
            self.error(_("The 'ylabels' argument length do no match array row "
                         "number"))
            return False
        if not is_record_array:
            dtn = data.dtype.name
            if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') \
               and not dtn.startswith('unicode'):
                arr = _("%s arrays") % data.dtype.name
                self.error(_("%s are currently not supported") % arr)
                return False
        
        self.layout = QGridLayout()
        self.setLayout(self.layout)
        self.setWindowIcon(ima.icon('arredit'))
        if title:
            title = to_text_string(title) + " - " + _("NumPy array")
        else:
            title = _("Array editor")
        if readonly:
            title += ' (' + _('read only') + ')'
        self.setWindowTitle(title)
        self.resize(600, 500)
        
        # Stack widget
        self.stack = QStackedWidget(self)
        if is_record_array:
            for name in data.dtype.names:
                self.stack.addWidget(ArrayEditorWidget(self, data[name],
                                                   readonly, xlabels, ylabels))
        elif is_masked_array:
            self.stack.addWidget(ArrayEditorWidget(self, data, readonly,
                                                   xlabels, ylabels))
            self.stack.addWidget(ArrayEditorWidget(self, data.data, readonly,
                                                   xlabels, ylabels))
            self.stack.addWidget(ArrayEditorWidget(self, data.mask, readonly,
                                                   xlabels, ylabels))
        elif data.ndim == 3:
            pass
        else:
            self.stack.addWidget(ArrayEditorWidget(self, data, readonly,
                                                   xlabels, ylabels))
        self.arraywidget = self.stack.currentWidget()
        if self.arraywidget:
            self.arraywidget.model.dataChanged.connect(
                                                    self.save_and_close_enable)
        self.stack.currentChanged.connect(self.current_widget_changed)
        self.layout.addWidget(self.stack, 1, 0)

        # Buttons configuration
        btn_layout = QHBoxLayout()
        if is_record_array or is_masked_array or data.ndim == 3:
            if is_record_array:
                btn_layout.addWidget(QLabel(_("Record array fields:")))
                names = []
                for name in data.dtype.names:
                    field = data.dtype.fields[name]
                    text = name
                    if len(field) >= 3:
                        title = field[2]
                        if not is_text_string(title):
                            title = repr(title)
                        text += ' - '+title
                    names.append(text)
            else:
                names = [_('Masked data'), _('Data'), _('Mask')]
            if data.ndim == 3:
                # QSpinBox
                self.index_spin = QSpinBox(self, keyboardTracking=False)
                self.index_spin.valueChanged.connect(self.change_active_widget)
                # QComboBox
                names = [str(i) for i in range(3)]
                ra_combo = QComboBox(self)
                ra_combo.addItems(names)
                ra_combo.currentIndexChanged.connect(self.current_dim_changed)    
                # Adding the widgets to layout
                label = QLabel(_("Axis:"))
                btn_layout.addWidget(label)
                btn_layout.addWidget(ra_combo)
                self.shape_label = QLabel()
                btn_layout.addWidget(self.shape_label)
                label = QLabel(_("Index:"))
                btn_layout.addWidget(label)
                btn_layout.addWidget(self.index_spin)
                self.slicing_label = QLabel()
                btn_layout.addWidget(self.slicing_label)
                # set the widget to display when launched
                self.current_dim_changed(self.last_dim)
            else:
                ra_combo = QComboBox(self)
                ra_combo.currentIndexChanged.connect(self.stack.setCurrentIndex)
                ra_combo.addItems(names)
                btn_layout.addWidget(ra_combo)
            if is_masked_array:
                label = QLabel(_("<u>Warning</u>: changes are applied separately"))
                label.setToolTip(_("For performance reasons, changes applied "\
                                   "to masked array won't be reflected in "\
                                   "array's data (and vice-versa)."))
                btn_layout.addWidget(label)

        btn_layout.addStretch()

        if not readonly:
            self.btn_save_and_close = QPushButton(_('Save and Close'))
            self.btn_save_and_close.setDisabled(True)
            self.btn_save_and_close.clicked.connect(self.accept)
            btn_layout.addWidget(self.btn_save_and_close)

        self.btn_close = QPushButton(_('Close'))
        self.btn_close.setAutoDefault(True)
        self.btn_close.setDefault(True)
        self.btn_close.clicked.connect(self.reject)
        btn_layout.addWidget(self.btn_close)
        self.layout.addLayout(btn_layout, 2, 0)

        self.setMinimumSize(400, 300)
        
        # Make the dialog act as a window
        self.setWindowFlags(Qt.Window)
        
        return True

    @Slot(QModelIndex, QModelIndex)
    def save_and_close_enable(self, left_top, bottom_right):
        """Handle the data change event to enable the save and close button."""
        if self.btn_save_and_close:
            self.btn_save_and_close.setEnabled(True)
            self.btn_save_and_close.setAutoDefault(True)
            self.btn_save_and_close.setDefault(True)

    def current_widget_changed(self, index):
        self.arraywidget = self.stack.widget(index)
        self.arraywidget.model.dataChanged.connect(self.save_and_close_enable)
            
    def change_active_widget(self, index):
        """
        This is implemented for handling negative values in index for
        3d arrays, to give the same behavior as slicing
        """
        string_index = [':']*3
        string_index[self.last_dim] = '<font color=red>%i</font>'
        self.slicing_label.setText((r"Slicing: [" + ", ".join(string_index) +
                                "]") % index)
        if index < 0:
            data_index = self.data.shape[self.last_dim] + index
        else:
            data_index = index
        slice_index = [slice(None)]*3
        slice_index[self.last_dim] = data_index

        stack_index = self.dim_indexes[self.last_dim].get(data_index)
        if stack_index is None:
            stack_index = self.stack.count()
            try:
                self.stack.addWidget(ArrayEditorWidget(
                    self, self.data[tuple(slice_index)]))
            except IndexError:  # Handle arrays of size 0 in one axis
                self.stack.addWidget(ArrayEditorWidget(self, self.data))
            self.dim_indexes[self.last_dim][data_index] = stack_index
            self.stack.update()
        self.stack.setCurrentIndex(stack_index)

    def current_dim_changed(self, index):
        """
        This change the active axis the array editor is plotting over
        in 3D
        """
        self.last_dim = index
        string_size = ['%i']*3
        string_size[index] = '<font color=red>%i</font>'
        self.shape_label.setText(('Shape: (' + ', '.join(string_size) +
                                 ')    ') % self.data.shape)
        if self.index_spin.value() != 0:
            self.index_spin.setValue(0)
        else:
            # this is done since if the value is currently 0 it does not emit
            # currentIndexChanged(int)
            self.change_active_widget(0)
        self.index_spin.setRange(-self.data.shape[index],
                                 self.data.shape[index]-1)

    @Slot()
    def accept(self):
        """Reimplement Qt method"""
        for index in range(self.stack.count()):
            self.stack.widget(index).accept_changes()
        QDialog.accept(self)
        
    def get_value(self):
        """Return modified array -- this is *not* a copy"""
        # It is import to avoid accessing Qt C++ object as it has probably
        # already been destroyed, due to the Qt.WA_DeleteOnClose attribute
        return self.data

    def error(self, message):
        """An error occured, closing the dialog box"""
        QMessageBox.critical(self, _("Array editor"), message)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.reject()

    @Slot()
    def reject(self):
        """Reimplement Qt method"""
        if self.arraywidget is not None:
            for index in range(self.stack.count()):
                self.stack.widget(index).reject_changes()
        QDialog.reject(self)
Пример #9
0
    def setup_ui(self):
        """Set up Ui of Context Menu."""
        self.setStyleSheet("""
            #{}{{
                min-width:11.29em;
            }}""".format(self.objectName()))
        hbl = QHBoxLayout(self)

        btn = QPushButton(self.name, self)
        hbl.addWidget(btn)
        btn.setEnabled(True)
        btn.setAutoDefault(False)

        lbl = QLabel(self)
        hbl.addWidget(lbl)
        sz_pol = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        sz_pol.setHorizontalStretch(1)
        lbl.setSizePolicy(sz_pol)
        lbl.setMouseTracking(True)
        lbl.setAcceptDrops(True)
        lbl.setTextInteractionFlags(Qt.TextEditorInteraction)
        self.new_string_signal.connect(lbl.setText)

        menu = QMenu(btn)
        btn.setContextMenuPolicy(Qt.CustomContextMenu)
        btn.setMenu(menu)
        btn.clicked.connect(btn.showMenu)

        act = menu.addAction('Get From &File')
        act.setIcon(qta.icon('mdi.file-download-outline'))
        act.triggered.connect(self._load_orbit_from_file)
        act = menu.addAction('Get From &ServConf')
        act.setIcon(qta.icon('mdi.cloud-download-outline'))
        act.triggered.connect(self._load_orbit_from_servconf)
        menu2 = menu.addMenu('Get from &IOC')
        menu2.setIcon(qta.icon('mdi.download-network-outline'))
        if self._csorb.acc == 'SI':
            act = menu2.addAction('&SlowOrb')
            act.setIcon(qta.icon('mdi.turtle'))
            act.triggered.connect(_part(self._register_orbit, 'orb'))
        if self._csorb.isring:
            act = menu2.addAction('&MTurnOrb')
            act.setIcon(qta.icon('mdi.alarm-multiple'))
            act.triggered.connect(_part(self._register_orbit, 'mti'))
        act = menu2.addAction('S&PassOrb')
        act.setIcon(qta.icon('mdi.clock-fast'))
        act.triggered.connect(_part(self._register_orbit, 'sp'))
        act = menu2.addAction('&RefOrb')
        act.triggered.connect(_part(self._register_orbit, 'ref'))
        act = menu2.addAction('&OfflineOrb')
        act.setIcon(qta.icon('mdi.signal-off'))
        act.triggered.connect(_part(self._register_orbit, 'off'))
        act = menu2.addAction('&BPM Offsets')
        act.setIcon(qta.icon('mdi.currency-sign'))
        act.triggered.connect(_part(self._register_orbit, 'bpm'))
        act = menu2.addAction('RespMat')
        act.setIcon(qta.icon('mdi.matrix'))
        act.triggered.connect(self._open_matrix_sel)
        act = menu.addAction('&Edit Orbit')
        act.setIcon(qta.icon('mdi.table-edit'))
        act.triggered.connect(self._edit_orbit)
        if self._csorb.acc == 'SI':
            act = menu.addAction('Create &Bump')
            act.setIcon(
                qta.icon('mdi.chart-bell-curve',
                         scale_factor=1.2,
                         offset=(-0.2, 0.2)))
            act.triggered.connect(self._create_bump)
        act = menu.addAction('&Clear')
        act.setIcon(qta.icon('mdi.delete-empty'))
        act.triggered.connect(self._reset_orbit)
        act = menu.addAction('Save To File')
        act.setIcon(qta.icon('mdi.file-upload-outline'))
        act.triggered.connect(self._save_orbit_to_file)
        act = menu.addAction('Save To ServConf')
        act.setIcon(qta.icon('mdi.cloud-upload-outline'))
        act.triggered.connect(self._save_orbit_to_servconf)
class ParamSweepWidget(QDialog):
    """
    A dialog box containing a workflow finished message. Also includes a button to open the output folder
    and a button to close the dialog box

    Params:
        output_folder (Path):       The output folder to open when the corresponding button is clicked by the user.
    """
    def __init__(self, param_set: Dict[str, Any], step_number, controller):
        super().__init__()
        # Track UI elements
        self.live_count: QLabel = None
        self.progress_bar: QProgressBar = None
        self.inputs: Dict[str, QFrame] = dict()
        self.default_sweep_values: Dict = dict()
        self.layout: QVBoxLayout = QVBoxLayout()
        # State
        self.controller = controller
        self.step_number: int = step_number
        self.param_set: Dict[str, Any] = param_set
        rows: List[FormRow] = self._create_sweep_ui()

        # Format UI on init
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.addWidget(self._create_buttons())
        self.setLayout(Form(rows))
        self.setStyleSheet(get_stylesheet(self.controller.viewer.get_theme()))
        self.setWindowTitle("Parameter Sweep")

    def _create_sweep_ui(self) -> List[FormRow]:
        """
        Populate the sweep widget with this workflow step's parameters and their default values as well as other
        UI elements needed to run a sweep.

         Params:
            none
        """
        rows: List[FormRow] = list()
        # add ui elements in order
        rows.append(FormRow("", widget=self.create_sweep_headers()))

        # convert parameter set to form rows
        default_params: Dict = self.controller.model.active_workflow.workflow_definition.steps[
            self.step_number].function.parameters
        if self.param_set:
            for key, value in self.param_set.items():
                # sometimes multiple unique params are in one list, need to separate out for UI
                if isinstance(value, list):
                    if not isinstance(value[0], str):
                        i = 1
                        for _ in value:
                            sweep_inputs: QFrame = QFrame()
                            sweep_inputs.setLayout(QHBoxLayout())
                            # get default value
                            min_value: int = default_params[key][i -
                                                                 1].min_value
                            max_value: int = default_params[key][i -
                                                                 1].max_value
                            step_size: float = (max_value - min_value) / 2

                            # Create UI Elements and populate with default values
                            min_input: QLineEdit = QLineEdit()
                            min_input.setText(str(min_value))
                            min_input.editingFinished.connect(
                                self._on_change_textbox)
                            sweep_inputs.layout().addWidget(min_input)

                            max_input: QLineEdit = QLineEdit()
                            max_input.setText(str(max_value))
                            max_input.editingFinished.connect(
                                self._on_change_textbox)
                            sweep_inputs.layout().addWidget(max_input)

                            step_input = QLineEdit()
                            step_input.setText(str(step_size))
                            step_input.editingFinished.connect(
                                self._on_change_textbox)
                            sweep_inputs.layout().addWidget(step_input)

                            # Reset button for row (reset to default values)
                            reset_button: QPushButton = QPushButton("reset")
                            reset_button.setStyleSheet("border: none;")
                            # pass the key and value as values for calling function later on
                            reset_button.clicked.connect(
                                partial(
                                    lambda k, val: self._reset_row_to_default(
                                        k, val), key, i))
                            sweep_inputs.layout().addWidget(reset_button)

                            # store the index of this parameter in its list appended to the parameter key
                            self.inputs[key + str(i)] = sweep_inputs
                            rows.append(
                                FormRow(f"{key} {i}", widget=sweep_inputs))
                            i = i + 1
                else:
                    # most params are single entries in the param dictionary
                    # for params that are a dropdown
                    if default_params[key][0].widget_type.name == "DROPDOWN":
                        dropdown = QComboBox()
                        dropdown.setStyleSheet(
                            "QComboBox { combobox-popup: 0; }")
                        dropdown.addItems(default_params[key][0].options)
                        self.inputs[key] = dropdown
                        rows.append(FormRow(key, widget=dropdown))
                    else:
                        # for typical sweep params
                        sweep_inputs: QFrame = QFrame()
                        sweep_inputs.setLayout(QHBoxLayout())
                        # get the default values
                        min_value: int = default_params[key][0].min_value
                        max_value: int = default_params[key][0].max_value
                        step_size: float = (max_value - min_value) / 2

                        # Create UI elements and populate with default values
                        min_input: QLineEdit = QLineEdit()
                        min_input.setText(str(min_value))
                        min_input.editingFinished.connect(
                            self._on_change_textbox)
                        sweep_inputs.layout().addWidget(min_input)

                        max_input: QLineEdit = QLineEdit()
                        max_input.setText(str(max_value))
                        max_input.editingFinished.connect(
                            self._on_change_textbox)
                        sweep_inputs.layout().addWidget(max_input)

                        step_input: QLineEdit = QLineEdit()
                        step_input.setText(str(step_size))
                        step_input.editingFinished.connect(
                            self._on_change_textbox)
                        sweep_inputs.layout().addWidget(step_input)
                        self.inputs[key] = sweep_inputs

                        # Create button to reset back to default values
                        reset_button: QPushButton = QPushButton("reset")
                        reset_button.setStyleSheet("border: none;")
                        reset_button.clicked.connect(
                            lambda: self._reset_row_to_default(key))
                        sweep_inputs.layout().addWidget(reset_button)
                        rows.append(FormRow(key, widget=sweep_inputs))

        # Grab user input as str
        def_params_values: List[List[str]] = self.grab_ui_values(
            grab_combo=False)

        # Display how many images will be create with this sweep
        def_count: int = self.get_live_count(def_params_values)
        self.live_count = QLabel(f"{def_count} images will be created")
        self.live_count.setAlignment(qtpy.QtCore.Qt.AlignCenter)

        # Create progress bar with length of sweep
        self.create_progress_bar(bar_len=def_count)

        rows.append(FormRow("", widget=self.live_count))
        rows.append(FormRow("", widget=self.progress_bar))
        rows.append(FormRow("", widget=self._create_buttons()))
        return rows

    def _create_buttons(self) -> QFrame:
        """
        Creates buttons for the bottom of the dialog box, one for opening the output folder, and one for
            closing the dialog box

        Params:
            None

        Returns:
            (QFrame): A QFrame that has two horizontally laid out buttons, first button is Open output directory,
                second button is close.
        """
        buttons: QFrame = QFrame()
        buttons.setLayout(QHBoxLayout())
        self.run_sweep_button = QPushButton("Start Sweep")
        self.run_sweep_button.setToolTip(
            "Start sweep using the selected napari layer as the input image.")
        self.run_sweep_button.clicked.connect(self._run_sweep)
        self.run_sweep_button.setAutoDefault(False)
        close_button: QPushButton = QPushButton("Cancel")
        close_button.setToolTip("Cancel an active parameter sweep.")
        close_button.clicked.connect(self.cancel)
        close_button.setAutoDefault(False)
        buttons.layout().addWidget(self.run_sweep_button)
        buttons.layout().addWidget(close_button)
        return buttons

    def _run_sweep(self) -> None:
        """
        Initiate a sweep (called when run sweep button is pressed) with the values provided in the UI

        Params:
           none
        """
        inputs: List[List[str]] = self.grab_ui_values(grab_combo=False)
        count: int = self.get_live_count(inputs)
        if count > 20:
            # warn if too many images will be generated
            if self.warn_images_created(count) == 1024:
                self.set_run_in_progress()
                self.controller.run_step_sweep(self, self.grab_ui_values())
        else:
            self.set_run_in_progress()
            self.controller.run_step_sweep(self, self.grab_ui_values())

    def sanitize_ui_inputs(self, ui_inputs: List[List[str]]) -> None:
        """
        Check that all user inputs in the UI (passed as str) can be converted to a valid float
        Params:
           ui_inputs (List[List[str]]): inputs recieved from the UI as str
        """
        for i in ui_inputs:
            for j in i:
                try:
                    # ensure user input (passed as str) is a valid number
                    float(j)
                except ValueError:
                    raise ValueError(
                        "Please enter a single number or the min:step:max notation for sweeps"
                    )

    def warn_images_created(self, count: int) -> None:
        """
        Create a popup to warn a user that they will be creating a large number of layers by running a sweep.

        Params:
           count (int): number of images that user will create for warning
        """
        message: QMessageBox = QMessageBox()
        message.setText(f"{int(count)} result image layers will be created.")
        message.setStyleSheet(
            get_stylesheet(self.controller.viewer.get_theme()))
        message.setWindowTitle("Running Sweep")
        message.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        return message.exec_()

    def update_live_count(self, count: int) -> None:
        """
        Update the Live count on the param sweep widget

        Params:
            count (int): new live count to update with
        """
        if self.live_count:
            self.live_count.setText(f"{count } images will be created")

    def get_live_count(self, inputs: List[List[str]]) -> int:
        """
        Calculate how many images will be created with the user provided values for this sweep

        Params:
            inputs (List[List[str]]): user inputs from the paramsweepwidget to calculate how many total result layers
            will be created
        """
        # make sure all user inputs are valid numbers
        self.sanitize_ui_inputs(inputs)
        length: int = 1
        for sweeps in inputs:
            # multiplying the result layers for all parameters will give total result layers
            # (ex: if param1 creates 5 combinations and param2 creates 5 combinations, there will be
            # 25 result layers created 5x5=25)
            length = length * self.get_sweep_len(float(
                sweeps[0]), float(sweeps[1]), float(sweeps[2]))
        return length

    def get_sweep_len(self, min: float, step: float, max: float) -> int:
        """
        Calculate how many result layers will be created for one parameter's sweep values

        Params:
            min (float): starting range of sweep provided by user
            step (float): increment value of sweep provided by user
            max (float): end value of sweep provided by user
        """
        i: float = min
        count: int = 0
        while i <= max:
            i = i + step
            count = count + 1
        return count

    def grab_ui_values(self, grab_combo=True) -> List[List[str]]:
        """
        Update the Live count on the param sweep widget

        Params:
            count (int): new live count to update with
        """
        inputs: List[List[str]] = list()
        for widget in self.inputs.values():
            # grab values from combobox (when calling run_sweep function)
            if grab_combo:
                # ui is min, max, step
                # transform into min, step, max
                try:
                    # is a set of numbers for sweep
                    inputs.append([
                        widget.children()[1].text(),
                        widget.children()[3].text(),
                        widget.children()[2].text()
                    ])
                except IndexError:
                    # is a combobox(string parameters)
                    inputs.append(widget.currentText())
                except AttributeError:
                    inputs.append(widget.currentText())
            else:
                # do not grab values from combobox (for checking inputs and getting size of sweeps)
                try:
                    inputs.append([
                        widget.children()[1].text(),
                        widget.children()[3].text(),
                        widget.children()[2].text()
                    ])
                except Exception:
                    pass
        return inputs

    def create_progress_bar(self, bar_len: int = 10) -> QProgressBar:
        """
        Create a progress bar with the given length

        Params:
            bar_len (int): length of progress bar to be created
        """
        self.progress_bar: QProgressBar = QProgressBar()
        self.progress_bar.setRange(0, bar_len)
        self.progress_bar.setValue(0)
        self.progress_bar.setTextVisible(False)
        return self.progress_bar

    def update_progress_bar_len(self, new_len: int) -> None:
        """
        Update the progress bar so it has the given length

        Params:
            new_len (int): new length to update progress bar
        """
        if self.progress_bar:
            self.progress_bar.setRange(0, new_len)
            self.progress_bar.setValue(0)

    def increment_progress_bar(self) -> None:
        """
        Increment the progress bar by one step

        Params:
            none
        """
        if self.controller.run_lock:
            self.progress_bar.setValue(self.progress_bar.value() + 1)

    def reset_progress_bar(self) -> None:
        """
        Reset the progress bar to 0

        Params:
            none
        """
        self.progress_bar.setValue(0)

    def set_progress_bar(self, val: int) -> None:
        """
        Set the progress bar to the value provided

        Params:
            val: value to set the progess bar to
        """
        self.progress_bar.setValue(val)

    def create_sweep_headers(self) -> QFrame:
        """
        Create headers for the sweep UI

        Params:
            none
        """
        # headers
        header: QFrame = QFrame()
        header.setLayout(QHBoxLayout())
        label: QLabel = QLabel("min")
        label.setAlignment(Qt.AlignCenter)
        header.layout().addWidget(label)
        label: QLabel = QLabel("max")
        label.setAlignment(Qt.AlignCenter)
        header.layout().addWidget(label)
        label: QLabel = QLabel("step size")
        label.setAlignment(Qt.AlignCenter)
        header.layout().addWidget(label)
        return header

    def _on_change_textbox(self) -> None:
        """
        Event listener called when textboxes in widget are edited

        Params:
            none
        """
        # grab new values
        inputs: List[List[str]] = self.grab_ui_values(grab_combo=False)
        # calculate new count
        new_count: int = self.get_live_count(inputs)
        # update ui
        self.update_live_count(new_count)
        self.update_progress_bar_len(new_count)

    def set_run_in_progress(self) -> None:
        """
        function called when a run is in progress

        Params:
            none
        """
        self.run_sweep_button.setText("Cancel")

    def set_run_finished(self) -> None:
        """
        function called when a run is finished

        Params:
            none
        """
        self.run_sweep_button.setText("Run Sweep")
        self.run_sweep_button.clicked.connect(self._run_sweep)

    def cancel(self) -> None:
        """
        Cancel the current sweep

        Params:
            none
        """
        if self.controller.run_lock:
            # if running, cancel
            self.controller.cancel_run_all()
        else:
            # if not running, close window
            self.close()

    def _reset_row_to_default(self, key_of_row: str, list_index: int = None):
        """
        Reset a parameter row to its default values

        Params:
            key_of_row (str): Name of row in ui to change back to default values
            list_index (int): index of parameter if there are multiple with the same name
        """
        default_param_set: Dict = self.controller.model.active_workflow.workflow_definition.steps[
            self.step_number].function.parameters

        if list_index:
            input_boxes: List = self.inputs[key_of_row +
                                            str(list_index)].children()
        else:
            input_boxes = self.inputs[key_of_row].children()

        if list_index:
            default_params = default_param_set[key_of_row][list_index - 1]
        else:
            default_params = default_param_set[key_of_row][0]

        # reset min value
        input_boxes[1].setText(str(default_params.min_value))
        # reset max value
        input_boxes[2].setText(str(default_params.max_value))
        # reset step_size value
        input_boxes[3].setText(
            str((default_params.max_value - default_params.min_value) / 2))

        updated_values: List[List[str]] = self.grab_ui_values(grab_combo=False)
        self.update_live_count(self.get_live_count(updated_values))
Пример #11
0
class OpenTourDialog(QDialog):
    """Initial widget with tour."""

    ICON_SCALE_FACTOR = 0.7 if MAC else 0.75
    TITLE_FONT_SIZE = '19pt' if MAC else '16pt'
    CONTENT_FONT_SIZE = '15pt' if MAC else '12pt'
    BUTTONS_FONT_SIZE = '15pt' if MAC else '13pt'
    BUTTONS_PADDING = '6px' if MAC else '4px 10px'

    def __init__(self, parent, tour_function):
        super().__init__(parent)
        if MAC:
            flags = (self.windowFlags() | Qt.WindowStaysOnTopHint
                     & ~Qt.WindowContextHelpButtonHint)
        else:
            flags = self.windowFlags() & ~Qt.WindowContextHelpButtonHint
        self.setWindowFlags(flags)
        self.tour_function = tour_function

        # Image
        images_layout = QHBoxLayout()
        icon_filename = 'tour-spyder-logo'
        image_path = get_image_path(icon_filename)
        image = QPixmap(image_path)
        image_label = QLabel()
        image_height = image.height() * self.ICON_SCALE_FACTOR
        image_width = image.width() * self.ICON_SCALE_FACTOR
        image = image.scaled(image_width, image_height, Qt.KeepAspectRatio,
                             Qt.SmoothTransformation)
        image_label.setPixmap(image)

        images_layout.addStretch()
        images_layout.addWidget(image_label)
        images_layout.addStretch()
        if MAC:
            images_layout.setContentsMargins(0, -5, 20, 0)
        else:
            images_layout.setContentsMargins(0, -8, 35, 0)

        # Label
        tour_label_title = QLabel(_("Welcome to Spyder!"))
        tour_label_title.setStyleSheet(f"font-size: {self.TITLE_FONT_SIZE}")
        tour_label_title.setWordWrap(True)
        tour_label = QLabel(
            _("Check out our interactive tour to "
              "explore some of Spyder's panes and features."))
        tour_label.setStyleSheet(f"font-size: {self.CONTENT_FONT_SIZE}")
        tour_label.setWordWrap(True)
        tour_label.setFixedWidth(340)

        # Buttons
        buttons_layout = QHBoxLayout()
        dialog_tour_color = QStylePalette.COLOR_BACKGROUND_2
        start_tour_color = QStylePalette.COLOR_ACCENT_2
        start_tour_hover = QStylePalette.COLOR_ACCENT_3
        start_tour_pressed = QStylePalette.COLOR_ACCENT_4
        dismiss_tour_color = QStylePalette.COLOR_BACKGROUND_4
        dismiss_tour_hover = QStylePalette.COLOR_BACKGROUND_5
        dismiss_tour_pressed = QStylePalette.COLOR_BACKGROUND_6
        font_color = QStylePalette.COLOR_TEXT_1
        self.launch_tour_button = QPushButton(_('Start tour'))
        self.launch_tour_button.setStyleSheet(
            ("QPushButton {{ "
             "background-color: {background_color};"
             "border-color: {border_color};"
             "font-size: {font_size};"
             "color: {font_color};"
             "padding: {padding}}}"
             "QPushButton:hover:!pressed {{ "
             "background-color: {color_hover}}}"
             "QPushButton:pressed {{ "
             "background-color: {color_pressed}}}").format(
                 background_color=start_tour_color,
                 border_color=start_tour_color,
                 font_size=self.BUTTONS_FONT_SIZE,
                 font_color=font_color,
                 padding=self.BUTTONS_PADDING,
                 color_hover=start_tour_hover,
                 color_pressed=start_tour_pressed))
        self.launch_tour_button.setAutoDefault(False)
        self.dismiss_button = QPushButton(_('Dismiss'))
        self.dismiss_button.setStyleSheet(
            ("QPushButton {{ "
             "background-color: {background_color};"
             "border-color: {border_color};"
             "font-size: {font_size};"
             "color: {font_color};"
             "padding: {padding}}}"
             "QPushButton:hover:!pressed {{ "
             "background-color: {color_hover}}}"
             "QPushButton:pressed {{ "
             "background-color: {color_pressed}}}").format(
                 background_color=dismiss_tour_color,
                 border_color=dismiss_tour_color,
                 font_size=self.BUTTONS_FONT_SIZE,
                 font_color=font_color,
                 padding=self.BUTTONS_PADDING,
                 color_hover=dismiss_tour_hover,
                 color_pressed=dismiss_tour_pressed))
        self.dismiss_button.setAutoDefault(False)

        buttons_layout.addStretch()
        buttons_layout.addWidget(self.launch_tour_button)
        if not MAC:
            buttons_layout.addSpacing(10)
        buttons_layout.addWidget(self.dismiss_button)

        layout = QHBoxLayout()
        layout.addLayout(images_layout)

        label_layout = QVBoxLayout()
        label_layout.addWidget(tour_label_title)
        if not MAC:
            label_layout.addSpacing(3)
            label_layout.addWidget(tour_label)
        else:
            label_layout.addWidget(tour_label)
            label_layout.addSpacing(10)

        vertical_layout = QVBoxLayout()
        if not MAC:
            vertical_layout.addStretch()
            vertical_layout.addLayout(label_layout)
            vertical_layout.addSpacing(20)
            vertical_layout.addLayout(buttons_layout)
            vertical_layout.addStretch()
        else:
            vertical_layout.addLayout(label_layout)
            vertical_layout.addLayout(buttons_layout)

        general_layout = QHBoxLayout()
        if not MAC:
            general_layout.addStretch()
            general_layout.addLayout(layout)
            general_layout.addSpacing(1)
            general_layout.addLayout(vertical_layout)
            general_layout.addStretch()
        else:
            general_layout.addLayout(layout)
            general_layout.addLayout(vertical_layout)

        self.setLayout(general_layout)

        self.launch_tour_button.clicked.connect(self._start_tour)
        self.dismiss_button.clicked.connect(self.close)
        self.setStyleSheet(f"background-color:{dialog_tour_color}")
        self.setContentsMargins(18, 40, 18, 40)
        if not MAC:
            self.setFixedSize(640, 280)

    def _start_tour(self):
        self.close()
        self.tour_function()
Пример #12
0
class CondaPackagesWidget(QWidget):
    """
    Conda Packages Widget.
    """

    # Location of updated repo.json files from continuum/binstar
    CONDA_CONF_PATH = get_conf_path('repo')

    # Location of continuum/anaconda default repos shipped with conda-manager
    DATA_PATH = get_module_data_path()

    # file inside DATA_PATH with metadata for conda packages
    DATABASE_FILE = 'packages.ini'

    sig_worker_ready = Signal()
    sig_packages_ready = Signal()
    sig_environment_created = Signal(object, object)
    sig_environment_removed = Signal(object, object)
    sig_environment_cloned = Signal(object, object)
    sig_channels_updated = Signal(tuple, tuple)  # channels, active_channels
    sig_process_cancelled = Signal()
    sig_next_focus = Signal()
    sig_packages_busy = Signal()

    def __init__(self,
                 parent,
                 name=None,
                 prefix=None,
                 channels=(),
                 active_channels=(),
                 conda_url='https://conda.anaconda.org',
                 conda_api_url='https://api.anaconda.org',
                 setup=True,
                 data_directory=None,
                 extra_metadata={}):

        super(CondaPackagesWidget, self).__init__(parent)

        # Check arguments: active channels, must be witbhin channels
        for ch in active_channels:
            if ch not in channels:
                raise Exception("'active_channels' must be also within "
                                "'channels'")

        if data_directory is None:
            data_directory = self.CONDA_CONF_PATH

        self._parent = parent
        self._current_action_name = ''
        self._hide_widgets = False
        self._metadata = extra_metadata  # From repo.continuum
        self._metadata_links = {}  # Bundled metadata
        self.api = ManagerAPI()
        self.busy = False
        self.data_directory = data_directory
        self.conda_url = conda_url
        self.conda_api_url = conda_api_url
        self.name = name
        self.package_blacklist = []
        self.prefix = prefix
        self.root_prefix = self.api.ROOT_PREFIX
        self.style_sheet = None
        self.message = ''
        self.apply_actions_dialog = None
        self.conda_errors = []
        self.message_box_error = None
        self.token = None

        if channels:
            self._channels = channels
            self._active_channels = active_channels
        else:
            self._channels = self.api.conda_get_condarc_channels()
            self._active_channels = self._channels[:]

        try:
            import spyderlib.utils.icon_manager as ima
            icon_options = ima.icon('tooloptions')
        except Exception:
            import qtawesome as qta
            icon_options = qta.icon('fa.cog')

        # Widgets
        self.cancel_dialog = ClosePackageManagerDialog
        self.bbox = QDialogButtonBox(Qt.Horizontal)
        self.button_cancel = QPushButton('Cancel')
        self.button_channels = QPushButton(_('Channels'))
        self.button_ok = QPushButton(_('Ok'))
        self.button_update = QPushButton(_('Update index...'))
        self.button_apply = QPushButton(_('Apply'))
        self.button_clear = QPushButton(_('Clear'))
        self.button_options = QToolButton()
        self.combobox_filter = DropdownPackageFilter(self)
        self.frame_top = FramePackageTop()
        self.frame_bottom = FramePackageTop()
        self.progress_bar = ProgressBarPackage(self)
        self.status_bar = LabelPackageStatus(self)
        self.table = TableCondaPackages(self)
        self.textbox_search = LineEditSearch(self)
        self.widgets = [
            self.button_update, self.button_channels, self.combobox_filter,
            self.textbox_search, self.table, self.button_ok, self.button_apply,
            self.button_clear, self.button_options
        ]
        self.table_first_row = FirstRowWidget(
            widget_before=self.textbox_search)
        self.table_last_row = LastRowWidget(widgets_after=[
            self.button_apply, self.button_clear, self.button_cancel,
            self.combobox_filter
        ])

        # Widget setup
        self.button_options.setPopupMode(QToolButton.InstantPopup)
        self.button_options.setIcon(icon_options)
        self.button_options.setAutoRaise(True)

        max_height = self.status_bar.fontMetrics().height()
        max_width = self.textbox_search.fontMetrics().width('M' * 23)
        self.bbox.addButton(self.button_ok, QDialogButtonBox.ActionRole)
        self.button_ok.setAutoDefault(True)
        self.button_ok.setDefault(True)
        self.button_ok.setMaximumSize(QSize(0, 0))
        self.button_ok.setVisible(False)
        self.combobox_filter.addItems([k for k in C.COMBOBOX_VALUES_ORDERED])
        self.combobox_filter.setMinimumWidth(120)
        self.progress_bar.setMaximumHeight(max_height * 1.2)
        self.progress_bar.setMaximumWidth(max_height * 12)
        self.progress_bar.setTextVisible(False)
        self.progress_bar.setVisible(False)
        self.setMinimumSize(QSize(480, 300))
        self.setWindowTitle(_("Conda Package Manager"))
        self.status_bar.setFixedHeight(max_height * 1.5)
        self.textbox_search.setMaximumWidth(max_width)
        self.textbox_search.setPlaceholderText('Search Packages')
        self.table_first_row.setMaximumHeight(0)
        self.table_last_row.setMaximumHeight(0)
        self.table_last_row.setVisible(False)
        self.table_first_row.setVisible(False)

        # Layout
        top_layout = QHBoxLayout()
        top_layout.addWidget(self.combobox_filter)
        top_layout.addWidget(self.button_channels)
        top_layout.addWidget(self.button_update)
        top_layout.addWidget(self.textbox_search)
        top_layout.addStretch()
        top_layout.addWidget(self.button_options)

        middle_layout = QVBoxLayout()
        middle_layout.addWidget(self.table_first_row)
        middle_layout.addWidget(self.table)
        middle_layout.addWidget(self.table_last_row)

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(self.status_bar)
        bottom_layout.addStretch()
        bottom_layout.addWidget(self.progress_bar)
        bottom_layout.addWidget(self.button_cancel)
        bottom_layout.addWidget(self.button_apply)
        bottom_layout.addWidget(self.button_clear)

        layout = QVBoxLayout(self)
        layout.addLayout(top_layout)
        layout.addLayout(middle_layout)
        layout.addLayout(bottom_layout)
        layout.addSpacing(6)
        self.setLayout(layout)

        self.setTabOrder(self.combobox_filter, self.button_channels)
        self.setTabOrder(self.button_channels, self.button_update)
        self.setTabOrder(self.button_update, self.textbox_search)
        self.setTabOrder(self.textbox_search, self.table_first_row)
        self.setTabOrder(self.table, self.table_last_row)
        self.setTabOrder(self.table_last_row, self.button_apply)
        self.setTabOrder(self.button_apply, self.button_clear)
        self.setTabOrder(self.button_clear, self.button_cancel)

        # Signals and slots
        self.api.sig_repodata_updated.connect(self._repodata_updated)
        self.combobox_filter.currentIndexChanged.connect(self.filter_package)
        self.button_apply.clicked.connect(self.apply_multiple_actions)
        self.button_clear.clicked.connect(self.clear_actions)
        self.button_cancel.clicked.connect(self.cancel_process)
        self.button_channels.clicked.connect(self.show_channels_dialog)
        self.button_update.clicked.connect(self.update_package_index)
        self.textbox_search.textChanged.connect(self.search_package)
        self.table.sig_conda_action_requested.connect(self._run_conda_action)
        self.table.sig_actions_updated.connect(self.update_actions)
        self.table.sig_pip_action_requested.connect(self._run_pip_action)
        self.table.sig_status_updated.connect(self.update_status)
        self.table.sig_next_focus.connect(self.table_last_row.handle_tab)
        self.table.sig_previous_focus.connect(
            lambda: self.table_first_row.widget_before.setFocus())
        self.table_first_row.sig_enter_first.connect(self._handle_tab_focus)
        self.table_last_row.sig_enter_last.connect(self._handle_backtab_focus)

        # Setup
        self.api.client_set_domain(conda_api_url)
        self.api.set_data_directory(self.data_directory)
        self._load_bundled_metadata()
        self.update_actions(0)

        if setup:
            self.set_environment(name=name, prefix=prefix)
            self.setup()

    # --- Helpers
    # -------------------------------------------------------------------------
    def _handle_tab_focus(self):
        self.table.setFocus()
        if self.table.proxy_model:
            index = self.table.proxy_model.index(0, 0)
            self.table.setCurrentIndex(index)

    def _handle_backtab_focus(self):
        self.table.setFocus()
        if self.table.proxy_model:
            row = self.table.proxy_model.rowCount() - 1
            index = self.table.proxy_model.index(row, 0)
            self.table.setCurrentIndex(index)

    # --- Callbacks
    # -------------------------------------------------------------------------
    def _load_bundled_metadata(self):
        """
        """
        logger.debug('')

        parser = cp.ConfigParser()
        db_file = CondaPackagesWidget.DATABASE_FILE
        with open(osp.join(self.DATA_PATH, db_file)) as f:
            parser.readfp(f)

        for name in parser.sections():
            metadata = {}
            for key, data in parser.items(name):
                metadata[key] = data
            self._metadata_links[name] = metadata

    def _setup_packages(self, worker, data, error):
        """
        """
        if error:
            logger.error(error)
        else:
            logger.debug('')

        combobox_index = self.combobox_filter.currentIndex()
        status = C.PACKAGE_STATUS[combobox_index]

        packages = worker.packages

        # Remove blacklisted packages
        for package in self.package_blacklist:
            if package in packages:
                packages.pop(package)
            for i, row in enumerate(data):
                if package == data[i][C.COL_NAME]:
                    data.pop(i)

        self.table.setup_model(packages, data, self._metadata_links)
        self.combobox_filter.setCurrentIndex(combobox_index)
        self.filter_package(status)

        if self._current_model_index:
            self.table.setCurrentIndex(self._current_model_index)
            self.table.verticalScrollBar().setValue(self._current_table_scroll)

        if error:
            self.update_status(error, False)
        self.sig_packages_ready.emit()
        self.table.setFocus()

    def get_logged_user_list_channels(self):
        channels = []
        for ch in self._active_channels:
            if self.conda_url in ch and 'repo.continuum' not in ch:
                channel = ch.split('/')[-1]
                channels.append(channel)
        return channels

    def _prepare_model_data(self, worker=None, output=None, error=None):
        """
        """
        if error:
            logger.error(error)
        else:
            logger.debug('')

        packages, apps = output
        #        worker = self.api.pip_list(prefix=self.prefix)
        #        worker.sig_finished.connect(self._pip_list_ready)
        logins = self.get_logged_user_list_channels()
        worker = self.api.client_multi_packages(logins=logins,
                                                access='private')
        worker.sig_finished.connect(self._user_private_packages_ready)
        worker.packages = packages
        worker.apps = apps

    def _user_private_packages_ready(self, worker, output, error):
        if error:
            logger.error(error)
        else:
            logger.debug('')

        packages = worker.packages
        apps = worker.apps
        worker = self.api.pip_list(prefix=self.prefix)
        worker.sig_finished.connect(self._pip_list_ready)
        worker.packages = packages
        worker.apps = apps

        #        private_packages = {}
        #        if output:
        #            all_private_packages = output
        #            for item in all_private_packages:
        #                name = item.get('name', '')
        #                public = item.get('public', True)
        #                package_types = item.get('package_types', [])
        #                latest_version = item.get('latest_version', '')
        #                if name and not public and 'conda' in package_types:
        #                    private_packages[name] = {'versions': item.get('versions', []),
        #                                              'app_entry': {},
        #                                              'type': {},
        #                                              'size': {},
        #                                              'latest_version': latest_version,
        #                                              }
        worker.private_packages = output

    def _pip_list_ready(self, worker, pip_packages, error):
        """
        """
        if error:
            logger.error(error)
        else:
            logger.debug('')

        packages = worker.packages
        private_packages = worker.private_packages
        linked_packages = self.api.conda_linked(prefix=self.prefix)
        data = self.api.client_prepare_packages_data(packages, linked_packages,
                                                     pip_packages,
                                                     private_packages)

        combobox_index = self.combobox_filter.currentIndex()
        status = C.PACKAGE_STATUS[combobox_index]

        # Remove blacklisted packages
        for package in self.package_blacklist:
            if package in packages:
                packages.pop(package)

            for i, row in enumerate(data):
                if package == data[i][C.COL_NAME]:
                    data.pop(i)

        self.table.setup_model(packages, data, self._metadata_links)
        self.combobox_filter.setCurrentIndex(combobox_index)
        self.filter_package(status)

        if self._current_model_index:
            self.table.setCurrentIndex(self._current_model_index)
            self.table.verticalScrollBar().setValue(self._current_table_scroll)

        if error:
            self.update_status(str(error), False)
        self.sig_packages_ready.emit()
        self.table.setFocus()

    def _repodata_updated(self, paths):
        """
        """
        worker = self.api.client_load_repodata(paths,
                                               extra_data={},
                                               metadata=self._metadata)
        worker.paths = paths
        worker.sig_finished.connect(self._prepare_model_data)

    def _metadata_updated(self, worker, path, error):
        """
        """
        if error:
            logger.error(error)
        else:
            logger.debug('')

        if path and osp.isfile(path):
            with open(path, 'r') as f:
                data = f.read()
            try:
                self._metadata = json.loads(data)
            except Exception:
                self._metadata = {}
        else:
            self._metadata = {}
        self.api.update_repodata(self._channels)

    # ---
    # -------------------------------------------------------------------------
    def _run_multiple_actions(self, worker=None, output=None, error=None):
        """
        """
        logger.error(str(error))

        if output and isinstance(output, dict):
            conda_error_type = output.get('error_type', None)
            conda_error = output.get('error', None)

            if conda_error_type or conda_error:
                self.conda_errors.append((conda_error_type, conda_error))
                logger.error((conda_error_type, conda_error))

        if self._multiple_process:
            status, func = self._multiple_process.popleft()
            self.update_status(status)
            worker = func()
            worker.sig_finished.connect(self._run_multiple_actions)
            worker.sig_partial.connect(self._partial_output_ready)
        else:
            if self.conda_errors and self.message_box_error:
                text = "The following errors occured:"
                error = ''
                for conda_error in self.conda_errors:
                    error += str(conda_error[0]) + ':\n'
                    error += str(conda_error[1]) + '\n\n'
                dlg = self.message_box_error(text=text,
                                             error=error,
                                             title='Conda process error')
                dlg.setMinimumWidth(400)
                dlg.exec_()

            self.update_status('', hide=False)
            self.setup()

    def _pip_process_ready(self, worker, output, error):
        """
        """
        if error is not None:
            status = _('there was an error')
            self.update_status(hide=False, message=status)
        else:
            self.update_status(hide=True)

        self.setup()

    def _conda_process_ready(self, worker, output, error):
        """
        """
        if error is not None:
            status = _('there was an error')
            self.update_status(hide=False, message=status)
        else:
            self.update_status(hide=True)

        conda_error = None
        conda_error_type = None
        if output and isinstance(output, dict):
            conda_error_type = output.get('error_type')
            conda_error = output.get('error')

            if conda_error_type or conda_error:
                logger.error((conda_error_type, conda_error))

        dic = self._temporal_action_dic

        if dic['action'] == C.ACTION_CREATE:
            self.sig_environment_created.emit(conda_error, conda_error_type)
        elif dic['action'] == C.ACTION_CLONE:
            self.sig_environment_cloned.emit(conda_error, conda_error_type)
        elif dic['action'] == C.ACTION_REMOVE_ENV:
            self.sig_environment_removed.emit(conda_error, conda_error_type)

        self.setup()

    def _partial_output_ready(self, worker, output, error):
        """
        """
        message = None
        progress = (0, 0)

        if isinstance(output, dict):
            progress = (output.get('progress',
                                   None), output.get('maxval', None))
            name = output.get('name', None)
            fetch = output.get('fetch', None)

            if fetch:
                message = "Downloading <b>{0}</b>...".format(fetch)

            if name:
                self._current_action_name = name
                message = "Linking <b>{0}</b>...".format(name)

        logger.debug(message)
        self.update_status(message, progress=progress)

    def _run_pip_action(self, package_name, action):
        """
        DEPRECATED
        """
        prefix = self.prefix

        if prefix == self.root_prefix:
            name = 'root'
        elif self.api.conda_environment_exists(prefix=prefix):
            name = osp.basename(prefix)
        else:
            name = prefix

        if action == C.ACTION_REMOVE:
            msgbox = QMessageBox.question(
                self, "Remove pip package: "
                "{0}".format(package_name), "Do you want to proceed?",
                QMessageBox.Yes | QMessageBox.No)
            if msgbox == QMessageBox.Yes:
                self.update_status()
                worker = self.api.pip_remove(prefix=self.prefix,
                                             pkgs=[package_name])
                worker.sig_finished.connect(self._pip_process_ready)
                status = (_('Removing pip package <b>') + package_name +
                          '</b>' + _(' from <i>') + name + '</i>')
                self.update_status(hide=True, message=status, progress=[0, 0])

    def _run_conda_action(self, package_name, action, version, versions,
                          packages_sizes):
        """
        DEPRECATED
        """
        prefix = self.prefix
        dlg = CondaPackageActionDialog(self, prefix, package_name, action,
                                       version, versions, packages_sizes,
                                       self._active_channels)

        if dlg.exec_():
            dic = {}

            self.status = 'Processing'
            self.update_status(hide=True)
            self.repaint()

            ver1 = dlg.label_version.text()
            ver2 = dlg.combobox_version.currentText()
            pkg = u'{0}={1}{2}'.format(package_name, ver1, ver2)
            dep = dlg.checkbox.checkState()
            state = dlg.checkbox.isEnabled()
            dlg.close()

            dic['pkg'] = pkg
            dic['dep'] = not (dep == 0 and state)
            dic['action'] = None
            self._run_conda_process(action, dic)

    def _run_conda_process(self, action, dic):
        """
        DEPRECTAED
        """
        self._temporal_action_dic = dic
        #        prefix = self.prefix
        #
        #        if prefix == self.root_prefix:
        #            name = 'root'
        #        elif self.api.conda_environment_exists(prefix=prefix):
        #            name = osp.basename(prefix)
        #        else:
        #            name = prefix

        if 'pkgs' in dic and 'dep' in dic:
            dep = dic['dep']
            pkgs = dic['pkgs']
            if not isinstance(pkgs, list):
                pkgs = [pkgs]

#        if (action == C.ACTION_INSTALL or action == C.ACTION_UPGRADE or
#           action == C.ACTION_DOWNGRADE):
#            status = _('Installing <b>') + dic['pkg'] + '</b>'
#            status = status + _(' into <i>') + name + '</i>'
#            worker = self.api.conda_install(prefix=prefix, pkgs=pkgs, dep=dep,
#                                            channels=self._active_channels)
#        elif action == C.ACTION_REMOVE:
#            status = (_('Removing <b>') + dic['pkg'] + '</b>' +
#                      _(' from <i>') + name + '</i>')
#            worker = self.api.conda_remove(pkgs[0], prefix=prefix)

# --- Environment management actions
        name = dic['name']
        if action == C.ACTION_CREATE:
            status = _('Creating environment <b>') + name + '</b>'
            worker = self.api.conda_create(name=name,
                                           pkgs=pkgs,
                                           channels=self._active_channels)
        elif action == C.ACTION_CLONE:
            clone = dic['clone']
            status = (_('Cloning ') + '<i>' + clone + _('</i> into <b>') +
                      name + '</b>')
            worker = self.api.conda_clone(clone, name=name)
        elif action == C.ACTION_REMOVE_ENV:
            status = _('Removing environment <b>') + name + '</b>'
            worker = self.api.conda_remove(name=name, all_=True)

        worker.sig_finished.connect(self._conda_process_ready)
        worker.sig_partial.connect(self._partial_output_ready)
        self.update_status(hide=True, message=status, progress=None)
        self._temporal_action_dic = dic
        return worker

    # Public API
    # -------------------------------------------------------------------------
    def prepare_model_data(self, packages, apps):
        """
        """
        logger.debug('')
        self._prepare_model_data(output=(packages, apps))

    # These should be private methods....
    def enable_widgets(self):
        """ """
        self.table.hide_columns()

    def disable_widgets(self):
        """ """
        self.table.hide_action_columns()

    def accept_channels_dialog(self):
        self.button_channels.setFocus()
        self.button_channels.toggle()

    def update_actions(self, number_of_actions):
        """
        """
        self.button_apply.setVisible(bool(number_of_actions))
        self.button_clear.setVisible(bool(number_of_actions))

    # --- Non UI API
    # -------------------------------------------------------------------------
    def setup(self, check_updates=False, blacklist=[], metadata={}):
        """
        Setup packages.

        Main triger method to download repodata, load repodata, prepare and
        updating the data model.

        Parameters
        ----------
        check_updates : bool
            If `True`, checks that the latest repodata is available on the
            listed channels. If `False`, the data will be loaded from the
            downloaded files without checking for newer versions.
        blacklist: list of str
            List of conda package names to be excluded from the actual package
            manager view.
        """
        self.sig_packages_busy.emit()

        if self.busy:
            logger.debug('Busy...')
            return
        else:
            logger.debug('')

        if blacklist:
            self.package_blacklist = [p.lower() for p in blacklist]

        if metadata:
            self._metadata = metadata

        self._current_model_index = self.table.currentIndex()
        self._current_table_scroll = self.table.verticalScrollBar().value()
        self.update_status('Updating package index', True)

        if check_updates:
            worker = self.api.update_metadata()
            worker.sig_finished.connect(self._metadata_updated)
        else:
            paths = self.api.repodata_files(channels=self._active_channels)
            self._repodata_updated(paths)

    def update_domains(self, anaconda_api_url=None, conda_url=None):
        """
        """
        logger.debug(str((anaconda_api_url, conda_url)))
        update = False

        if anaconda_api_url:
            if self.conda_api_url != anaconda_api_url:
                update = True

            self.conda_api_url = anaconda_api_url
            self.api.client_set_domain(anaconda_api_url)

        if conda_url:
            if self.conda_url != conda_url:
                update = True
            self.conda_url = conda_url

        if update:
            pass

    def set_environment(self, name=None, prefix=None):
        """
        This does not update the package manager!
        """
        logger.debug(str((name, prefix)))

        if prefix and self.api.conda_environment_exists(prefix=prefix):
            self.prefix = prefix
        elif name and self.api.conda_environment_exists(name=name):
            self.prefix = self.get_prefix_envname(name)
        else:
            self.prefix = self.root_prefix

    def set_token(self, token):
        self.token = token

    def update_channels(self, channels, active_channels):
        """
        """
        logger.debug(str((channels, active_channels)))

        if sorted(self._active_channels) != sorted(active_channels) or \
                sorted(self._channels) != sorted(channels):
            self._channels = channels
            self._active_channels = active_channels
            self.sig_channels_updated.emit(tuple(channels),
                                           tuple(active_channels))
            self.setup(check_updates=True)

    def update_style_sheet(self,
                           style_sheet=None,
                           extra_dialogs={},
                           palette={}):
        if style_sheet:
            self.style_sheet = style_sheet
            self.table.update_style_palette(palette=palette)
            self.textbox_search.update_style_sheet(style_sheet)
            self.setStyleSheet(style_sheet)

        if extra_dialogs:
            cancel_dialog = extra_dialogs.get('cancel_dialog', None)
            apply_actions_dialog = extra_dialogs.get('apply_actions_dialog',
                                                     None)
            message_box_error = extra_dialogs.get('message_box_error', None)
            if cancel_dialog:
                self.cancel_dialog = cancel_dialog
            if apply_actions_dialog:
                self.apply_actions_dialog = apply_actions_dialog
            if message_box_error:
                self.message_box_error = message_box_error

    # --- UI API
    # -------------------------------------------------------------------------
    def filter_package(self, value):
        """ """
        self.table.filter_status_changed(value)

    def show_channels_dialog(self):
        """
        Show the channels dialog.
        """
        button_channels = self.button_channels
        self.dlg = DialogChannels(self,
                                  channels=self._channels,
                                  active_channels=self._active_channels,
                                  conda_url=self.conda_url)
        self.dlg.update_style_sheet(style_sheet=self.style_sheet)
        button_channels.setDisabled(True)
        self.dlg.sig_channels_updated.connect(self.update_channels)
        self.dlg.rejected.connect(lambda: button_channels.setEnabled(True))
        self.dlg.rejected.connect(button_channels.toggle)
        self.dlg.rejected.connect(button_channels.setFocus)
        self.dlg.accepted.connect(self.accept_channels_dialog)

        geo_tl = button_channels.geometry().topLeft()
        tl = button_channels.parentWidget().mapToGlobal(geo_tl)
        x = tl.x() + 2
        y = tl.y() + button_channels.height()
        self.dlg.move(x, y)
        self.dlg.show()
        self.dlg.button_add.setFocus()

    def update_package_index(self):
        """ """
        self.setup(check_updates=True)

    def search_package(self, text):
        """ """
        self.table.search_string_changed(text)

    def apply_multiple_actions(self):
        """
        """
        logger.debug('')

        self.conda_errors = []

        prefix = self.prefix

        if prefix == self.root_prefix:
            name = 'root'
        elif self.api.conda_environment_exists(prefix=prefix):
            name = osp.basename(prefix)
        else:
            name = prefix

        actions = self.table.get_actions()

        if actions is None:
            return

        self._multiple_process = deque()

        pip_actions = actions[C.PIP_PACKAGE]
        conda_actions = actions[C.CONDA_PACKAGE]

        pip_remove = pip_actions.get(C.ACTION_REMOVE, [])
        conda_remove = conda_actions.get(C.ACTION_REMOVE, [])
        conda_install = conda_actions.get(C.ACTION_INSTALL, [])
        conda_upgrade = conda_actions.get(C.ACTION_UPGRADE, [])
        conda_downgrade = conda_actions.get(C.ACTION_DOWNGRADE, [])

        message = ''
        template_1 = '<li><b>{0}={1}</b></li>'
        template_2 = '<li><b>{0}: {1} -> {2}</b></li>'

        if pip_remove:
            temp = [
                template_1.format(i['name'], i['version_to'])
                for i in pip_remove
            ]
            message += ('The following pip packages will be removed: '
                        '<ul>' + ''.join(temp) + '</ul>')
        if conda_remove:
            temp = [
                template_1.format(i['name'], i['version_to'])
                for i in conda_remove
            ]
            message += ('<br>The following conda packages will be removed: '
                        '<ul>' + ''.join(temp) + '</ul>')
        if conda_install:
            temp = [
                template_1.format(i['name'], i['version_to'])
                for i in conda_install
            ]
            message += ('<br>The following conda packages will be installed: '
                        '<ul>' + ''.join(temp) + '</ul>')
        if conda_downgrade:
            temp = [
                template_2.format(i['name'], i['version_from'],
                                  i['version_to']) for i in conda_downgrade
            ]
            message += ('<br>The following conda packages will be downgraded: '
                        '<ul>' + ''.join(temp) + '</ul>')
        if conda_upgrade:
            temp = [
                template_2.format(i['name'], i['version_from'],
                                  i['version_to']) for i in conda_upgrade
            ]
            message += ('<br>The following conda packages will be upgraded: '
                        '<ul>' + ''.join(temp) + '</ul>')
        message += '<br>'

        if self.apply_actions_dialog:
            dlg = self.apply_actions_dialog(message, parent=self)
            dlg.update_style_sheet(style_sheet=self.style_sheet)
            reply = dlg.exec_()
        else:
            reply = QMessageBox.question(self,
                                         'Proceed with the following actions?',
                                         message,
                                         buttons=QMessageBox.Ok
                                         | QMessageBox.Cancel)

        if reply:
            # Pip remove
            for pkg in pip_remove:
                status = (_('Removing pip package <b>') + pkg['name'] +
                          '</b>' + _(' from <i>') + name + '</i>')
                pkgs = [pkg['name']]

                def trigger(prefix=prefix, pkgs=pkgs):
                    return lambda: self.api.pip_remove(prefix=prefix,
                                                       pkgs=pkgs)

                self._multiple_process.append([status, trigger()])

            # Process conda actions
            if conda_remove:
                status = (_('Removing conda packages <b>') + '</b>' +
                          _(' from <i>') + name + '</i>')
                pkgs = [i['name'] for i in conda_remove]

                def trigger(prefix=prefix, pkgs=pkgs):
                    return lambda: self.api.conda_remove(pkgs=pkgs,
                                                         prefix=prefix)

                self._multiple_process.append([status, trigger()])

            if conda_install:
                pkgs = [
                    '{0}={1}'.format(i['name'], i['version_to'])
                    for i in conda_install
                ]

                status = (_('Installing conda packages <b>') + '</b>' +
                          _(' on <i>') + name + '</i>')

                def trigger(prefix=prefix, pkgs=pkgs):
                    return lambda: self.api.conda_install(prefix=prefix,
                                                          pkgs=pkgs,
                                                          channels=self.
                                                          _active_channels,
                                                          token=self.token)

                self._multiple_process.append([status, trigger()])

            # Conda downgrade
            if conda_downgrade:
                status = (_('Downgrading conda packages <b>') + '</b>' +
                          _(' on <i>') + name + '</i>')

                pkgs = [
                    '{0}={1}'.format(i['name'], i['version_to'])
                    for i in conda_downgrade
                ]

                def trigger(prefix=prefix, pkgs=pkgs):
                    return lambda: self.api.conda_install(prefix=prefix,
                                                          pkgs=pkgs,
                                                          channels=self.
                                                          _active_channels,
                                                          token=self.token)

                self._multiple_process.append([status, trigger()])

            # Conda update
            if conda_upgrade:
                status = (_('Upgrading conda packages <b>') + '</b>' +
                          _(' on <i>') + name + '</i>')

                pkgs = [
                    '{0}={1}'.format(i['name'], i['version_to'])
                    for i in conda_upgrade
                ]

                def trigger(prefix=prefix, pkgs=pkgs):
                    return lambda: self.api.conda_install(prefix=prefix,
                                                          pkgs=pkgs,
                                                          channels=self.
                                                          _active_channels,
                                                          token=self.token)

                self._multiple_process.append([status, trigger()])

            self._run_multiple_actions()

    def clear_actions(self):
        """
        """
        self.table.clear_actions()

    def cancel_process(self):
        """
        Allow user to cancel an ongoing process.
        """
        logger.debug(str('process canceled by user.'))
        if self.busy:
            dlg = self.cancel_dialog()
            reply = dlg.exec_()
            if reply:
                self.update_status(hide=False, message='Process cancelled')
                self.api.conda_terminate()
                self.api.download_requests_terminate()
                self.api.conda_clear_lock()
                self.table.clear_actions()
                self.sig_process_cancelled.emit()
        else:
            QDialog.reject(self)

    def update_status(self, message=None, hide=True, progress=None, env=False):
        """
        Update status bar, progress bar display and widget visibility

        message : str
            Message to display in status bar.
        hide : bool
            Enable/Disable widgets.
        progress : [int, int]
            Show status bar progress. [0, 0] means spinning statusbar.
        """
        self.busy = hide
        for widget in self.widgets:
            widget.setDisabled(hide)
        self.table.verticalScrollBar().setValue(self._current_table_scroll)

        self.button_apply.setVisible(False)
        self.button_clear.setVisible(False)

        self.progress_bar.setVisible(hide)
        self.button_cancel.setVisible(hide)

        if message is not None:
            self.message = message

        if self.prefix == self.root_prefix:
            short_env = 'root'


#        elif self.api.environment_exists(prefix=self.prefix):
#            short_env = osp.basename(self.prefix)
        else:
            short_env = self.prefix

        if env:
            self.message = '{0} (<b>{1}</b>)'.format(
                self.message,
                short_env,
            )
        self.status_bar.setText(self.message)

        if progress is not None:
            current_progress, max_progress = 0, 0

            if progress[1]:
                max_progress = progress[1]

            if progress[0]:
                current_progress = progress[0]

            self.progress_bar.setMinimum(0)
            self.progress_bar.setMaximum(max_progress)
            self.progress_bar.setValue(current_progress)
        else:
            self.progress_bar.setMinimum(0)
            self.progress_bar.setMaximum(0)

    # --- Conda helpers
    # -------------------------------------------------------------------------
    def get_environment_prefix(self):
        """
        Returns the active environment prefix.
        """
        return self.prefix

    def get_environment_name(self):
        """
        Returns the active environment name if it is located in the default
        conda environments directory, otherwise it returns the prefix.
        """
        name = osp.basename(self.prefix)

        if not (name and self.api.environment_exists(name=name)):
            name = self.prefix

        return name

    def get_environments(self):
        """
        Get a list of conda environments located in the default conda
        environments directory.
        """
        return self.api.conda_get_envs()

    def get_prefix_envname(self, name):
        """
        Returns the prefix for a given environment by name.
        """
        return self.api.conda_get_prefix_envname(name)

    def get_package_versions(self, name):
        """ """
        return self.table.source_model.get_package_versions(name)

    # --- Conda actions
    # -------------------------------------------------------------------------
    def create_environment(self, name=None, prefix=None, packages=['python']):
        """ """
        # If environment exists already? GUI should take care of this
        # BUT the api call should simply set that env as the env
        dic = {}
        dic['name'] = name
        dic['prefix'] = prefix
        dic['pkgs'] = packages
        dic['dep'] = True  # Not really needed but for the moment!
        dic['action'] = C.ACTION_CREATE
        return self._run_conda_process(dic['action'], dic)

    def clone_environment(self, name=None, prefix=None, clone=None):
        dic = {}
        dic['name'] = name
        dic['prefix'] = prefix
        dic['clone'] = clone
        dic['pkgs'] = None
        dic['dep'] = True  # Not really needed but for the moment!
        dic['action'] = C.ACTION_CLONE
        return self._run_conda_process(dic['action'], dic)

    def remove_environment(self, name=None, prefix=None):
        dic = {}
        dic['name'] = name
        dic['pkgs'] = None
        dic['dep'] = True  # Not really needed but for the moment!
        dic['action'] = C.ACTION_REMOVE_ENV
        return self._run_conda_process(dic['action'], dic)

    # New api
    def show_login_dialog(self):
        pass

    def show_options_menu(self):
        pass
Пример #13
0
class OpticsAdjustSettings(SiriusDialog):
    """Auxiliar window to optics adjust settings."""

    updateSettings = Signal(str, str)

    def __init__(self, tuneconfig_currname, chromconfig_currname, parent=None):
        """Initialize object."""
        super().__init__(parent)
        self.setWindowTitle('Optics Adjust Settings')
        self.setObjectName('BOApp')
        self.tuneconfig_currname = tuneconfig_currname
        self.chromconfig_currname = chromconfig_currname
        self.conn_tuneparams = _ConfigDBClient(
            config_type='bo_tunecorr_params')
        self.conn_chromparams = _ConfigDBClient(
            config_type='bo_chromcorr_params')
        self._setupUi()

    def _setupUi(self):
        self.tune_settings = QWidget(self)
        self.tune_settings.setLayout(self._setupTuneSettings())
        self.le_tuneconfig.setText(self.tuneconfig_currname)
        self.chrom_settings = QWidget(self)
        self.chrom_settings.setLayout(self._setupChromSettings())
        self.le_chromconfig.setText(self.chromconfig_currname)
        self.bt_apply = QPushButton('Apply Settings', self)
        self.bt_apply.setStyleSheet("""min-width:8em; max-width:8em;""")
        self.bt_apply.clicked.connect(self._emitSettings)
        self.bt_apply.setAutoDefault(False)
        self.bt_apply.setDefault(False)
        hlay_apply = QHBoxLayout()
        hlay_apply.addItem(
            QSpacerItem(20, 60, QSzPlcy.Expanding, QSzPlcy.Ignored))
        hlay_apply.addWidget(self.bt_apply)

        tabs = QTabWidget(self)
        tabs.addTab(self.tune_settings, 'Tune')
        tabs.addTab(self.chrom_settings, 'Chromaticity')

        lay = QVBoxLayout()
        lay.addWidget(tabs)
        lay.addLayout(hlay_apply)
        self.setLayout(lay)

    def _setupTuneSettings(self):
        l_tuneconfig = QLabel('<h3>Tune Variation Config</h3>', self)
        l_tuneconfig.setAlignment(Qt.AlignCenter)
        self.le_tuneconfig = _ConfigLineEdit(parent=self,
                                             config_type='bo_tunecorr_params')
        self.le_tuneconfig.textChanged.connect(self._showTuneConfigData)

        label_tunemat = QLabel('<h4>Matrix</h4>', self)
        label_tunemat.setAlignment(Qt.AlignCenter)
        self.table_tunemat = QTableWidget(self)
        self.table_tunemat.setObjectName('tunemat')
        self.table_tunemat.setStyleSheet("""
            #tunemat{
                background-color: #efebe7;
                min-width: 22.14em;
                min-height: 6em; max-height: 6em;}""")
        self.table_tunemat.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.table_tunemat.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_tunemat.setRowCount(2)
        self.table_tunemat.setColumnCount(2)
        self.table_tunemat.setVerticalHeaderLabels(['  X', '  Y'])
        self.table_tunemat.setHorizontalHeaderLabels(['QF', 'QD'])
        self.table_tunemat.horizontalHeader().setStyleSheet("""
            min-height:1.55em; max-height:1.55em;""")
        self.table_tunemat.verticalHeader().setStyleSheet("""
            min-width:1.55em; max-width:1.55em;""")
        self.table_tunemat.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_tunemat.verticalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_tunemat.setSizePolicy(QSzPlcy.MinimumExpanding,
                                         QSzPlcy.Preferred)

        label_nomKL = QLabel('<h4>Nominal KL</h4>')
        label_nomKL.setAlignment(Qt.AlignCenter)
        self.table_nomKL = QTableWidget(self)
        self.table_nomKL.setObjectName('nomKL')
        self.table_nomKL.setStyleSheet("""
            #nomKL{
                background-color: #efebe7;
                min-width: 22.14em;
                min-height: 4em; max-height: 4em;}""")
        self.table_nomKL.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.table_nomKL.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_nomKL.setRowCount(1)
        self.table_nomKL.setColumnCount(2)
        self.table_nomKL.setVerticalHeaderLabels(['KL'])
        self.table_nomKL.setHorizontalHeaderLabels(['QF', 'QD'])
        self.table_nomKL.horizontalHeader().setStyleSheet("""
            min-height:1.55em; max-height:1.55em;""")
        self.table_nomKL.verticalHeader().setStyleSheet("""
            min-width:1.55em; max-width:1.55em;""")
        self.table_nomKL.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_nomKL.verticalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_nomKL.setSizePolicy(QSzPlcy.MinimumExpanding,
                                       QSzPlcy.Preferred)

        lay = QVBoxLayout()
        lay.addWidget(l_tuneconfig)
        lay.addWidget(self.le_tuneconfig)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding))
        lay.addWidget(label_tunemat)
        lay.addWidget(self.table_tunemat)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding))
        lay.addWidget(label_nomKL)
        lay.addWidget(self.table_nomKL)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding))

        return lay

    def _setupChromSettings(self):
        l_chromconfig = QLabel('<h3>Chromaticity Variation Config</h3>', self)
        l_chromconfig.setAlignment(Qt.AlignCenter)
        self.le_chromconfig = _ConfigLineEdit(
            parent=self, config_type='bo_chromcorr_params')
        self.le_chromconfig.textChanged.connect(self._showChromConfigData)

        l_chrommat = QLabel('<h4>Matrix</h4>', self)
        l_chrommat.setAlignment(Qt.AlignCenter)
        self.table_chrommat = QTableWidget(self)
        self.table_chrommat.setObjectName('chrommat')
        self.table_chrommat.setStyleSheet("""
            #chrommat{
                background-color: #efebe7;
                min-width: 22.14em;
                min-height: 6em; max-height: 6em;}""")
        self.table_chrommat.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.table_chrommat.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_chrommat.setRowCount(2)
        self.table_chrommat.setColumnCount(2)
        self.table_chrommat.setVerticalHeaderLabels(['  X', '  Y'])
        self.table_chrommat.setHorizontalHeaderLabels(['SF', 'SD'])
        self.table_chrommat.horizontalHeader().setStyleSheet("""
            min-height:1.55em; max-height:1.55em;""")
        self.table_chrommat.verticalHeader().setStyleSheet("""
            min-width:1.55em; max-width:1.55em;""")
        self.table_chrommat.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_chrommat.verticalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_chrommat.setSizePolicy(QSzPlcy.MinimumExpanding,
                                          QSzPlcy.Preferred)

        l_nomSL = QLabel('<h4>Nominal SL</h4>')
        l_nomSL.setAlignment(Qt.AlignCenter)
        self.table_nomSL = QTableWidget(self)
        self.table_nomSL.setObjectName('nomSL')
        self.table_nomSL.setStyleSheet("""
            #nomSL{
                background-color: #efebe7;
                min-width: 22.14em;
                min-height: 4em; max-height: 4em;}""")
        self.table_nomSL.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.table_nomSL.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_nomSL.setRowCount(1)
        self.table_nomSL.setColumnCount(2)
        self.table_nomSL.setVerticalHeaderLabels(['SL'])
        self.table_nomSL.setHorizontalHeaderLabels(['SF', 'SD'])
        self.table_nomSL.horizontalHeader().setStyleSheet("""
            min-height:1.55em; max-height:1.55em;""")
        self.table_nomSL.verticalHeader().setStyleSheet("""
            min-width:1.55em; max-width:1.55em;""")
        self.table_nomSL.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_nomSL.verticalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.table_nomSL.setSizePolicy(QSzPlcy.MinimumExpanding,
                                       QSzPlcy.Preferred)

        l_nomchrom = QLabel('<h4>Nominal Chrom</h4>')
        l_nomchrom.setAlignment(Qt.AlignCenter)
        self.label_nomchrom = QLabel()
        self.label_nomchrom.setAlignment(Qt.AlignCenter)

        lay = QVBoxLayout()
        lay.addWidget(l_chromconfig)
        lay.addWidget(self.le_chromconfig)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding))
        lay.addWidget(l_chrommat)
        lay.addWidget(self.table_chrommat)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding))
        lay.addWidget(l_nomSL)
        lay.addWidget(self.table_nomSL)
        lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding))
        lay.addWidget(l_nomchrom)
        lay.addWidget(self.label_nomchrom)

        return lay

    def _showTuneConfigData(self):
        try:
            name = self.le_tuneconfig.text()
            config = self.conn_tuneparams.get_config_value(name=name)
            mat = config['matrix']
            nomKL = config['nominal KLs']
        except _ConfigDBException as err:
            QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok)
        else:
            self.tuneconfig_currname = name
            self.table_tunemat.setItem(0, 0, QTableWidgetItem(str(mat[0][0])))
            self.table_tunemat.setItem(0, 1, QTableWidgetItem(str(mat[0][1])))
            self.table_tunemat.setItem(1, 0, QTableWidgetItem(str(mat[1][0])))
            self.table_tunemat.setItem(1, 1, QTableWidgetItem(str(mat[1][1])))
            self.table_tunemat.item(0, 0).setFlags(Qt.ItemIsEnabled)
            self.table_tunemat.item(0, 1).setFlags(Qt.ItemIsEnabled)
            self.table_tunemat.item(1, 0).setFlags(Qt.ItemIsEnabled)
            self.table_tunemat.item(1, 1).setFlags(Qt.ItemIsEnabled)
            self.table_nomKL.setItem(0, 0, QTableWidgetItem(str(nomKL[0])))
            self.table_nomKL.setItem(0, 1, QTableWidgetItem(str(nomKL[1])))
            self.table_nomKL.item(0, 0).setFlags(Qt.ItemIsEnabled)
            self.table_nomKL.item(0, 1).setFlags(Qt.ItemIsEnabled)

    def _showChromConfigData(self):
        try:
            name = self.le_chromconfig.text()
            config = self.conn_chromparams.get_config_value(name=name)
            mat = config['matrix']
            nomSL = config['nominal SLs']
            nomChrom = config['nominal chrom']
        except _ConfigDBException as err:
            QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok)
        else:
            self.chromconfig_currname = name
            self.table_chrommat.setItem(0, 0, QTableWidgetItem(str(mat[0][0])))
            self.table_chrommat.setItem(0, 1, QTableWidgetItem(str(mat[0][1])))
            self.table_chrommat.setItem(1, 0, QTableWidgetItem(str(mat[1][0])))
            self.table_chrommat.setItem(1, 1, QTableWidgetItem(str(mat[1][1])))
            self.table_chrommat.item(0, 0).setFlags(Qt.ItemIsEnabled)
            self.table_chrommat.item(0, 1).setFlags(Qt.ItemIsEnabled)
            self.table_chrommat.item(1, 0).setFlags(Qt.ItemIsEnabled)
            self.table_chrommat.item(1, 1).setFlags(Qt.ItemIsEnabled)
            self.table_nomSL.setItem(0, 0, QTableWidgetItem(str(nomSL[0])))
            self.table_nomSL.setItem(0, 1, QTableWidgetItem(str(nomSL[1])))
            self.table_nomSL.item(0, 0).setFlags(Qt.ItemIsEnabled)
            self.table_nomSL.item(0, 1).setFlags(Qt.ItemIsEnabled)
            self.label_nomchrom.setText(str(nomChrom))

    def _emitSettings(self):
        tuneconfig_name = self.le_tuneconfig.text()
        chromconfig_name = self.le_chromconfig.text()
        self.updateSettings.emit(tuneconfig_name, chromconfig_name)
        self.close()
Пример #14
0
class ProjectDialog(QDialog):
    """Project creation dialog."""

    sig_project_creation_requested = Signal(str, str, object)
    """
    This signal is emitted to request the Projects plugin the creation of a
    project.

    Parameters
    ----------
    project_path: str
        Location of project.
    project_type: str
        Type of project as defined by project types.
    project_packages: object
        Package to install. Currently not in use.
    """
    def __init__(self, parent, project_types):
        """Project creation dialog."""
        super(ProjectDialog, self).__init__(parent=parent)
        self.plugin = parent
        self._project_types = project_types
        self.project_data = {}

        self.setWindowFlags(self.windowFlags()
                            & ~Qt.WindowContextHelpButtonHint)

        self.project_name = None
        self.location = get_home_dir()

        # Widgets
        projects_url = "http://docs.spyder-ide.org/current/panes/projects.html"
        self.description_label = QLabel(
            _("Select a new or existing directory to create a new Spyder "
              "project in it. To learn more about projects, take a look at "
              "our <a href=\"{0}\">documentation</a>.").format(projects_url))
        self.description_label.setOpenExternalLinks(True)
        self.description_label.setWordWrap(True)

        self.groupbox = QGroupBox()
        self.radio_new_dir = QRadioButton(_("New directory"))
        self.radio_from_dir = QRadioButton(_("Existing directory"))

        self.label_project_name = QLabel(_('Project name'))
        self.label_location = QLabel(_('Location'))
        self.label_project_type = QLabel(_('Project type'))

        self.text_project_name = QLineEdit()
        self.text_location = QLineEdit(get_home_dir())
        self.combo_project_type = QComboBox()

        self.label_information = QLabel("")
        self.label_information.hide()

        self.button_select_location = create_toolbutton(
            self,
            triggered=self.select_location,
            icon=ima.icon('DirOpenIcon'),
            tip=_("Select directory"))
        self.button_cancel = QPushButton(_('Cancel'))
        self.button_create = QPushButton(_('Create'))

        self.bbox = QDialogButtonBox(Qt.Horizontal)
        self.bbox.addButton(self.button_cancel, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.button_create, QDialogButtonBox.ActionRole)

        # Widget setup
        self.radio_new_dir.setChecked(True)
        self.text_location.setEnabled(True)
        self.text_location.setReadOnly(True)
        self.button_cancel.setDefault(True)
        self.button_cancel.setAutoDefault(True)
        self.button_create.setEnabled(False)
        for (id_, name) in [(pt_id, pt.get_name())
                            for pt_id, pt in project_types.items()]:
            self.combo_project_type.addItem(name, id_)

        self.setWindowTitle(_('Create new project'))

        # Layouts
        layout_top = QHBoxLayout()
        layout_top.addWidget(self.radio_new_dir)
        layout_top.addSpacing(15)
        layout_top.addWidget(self.radio_from_dir)
        layout_top.addSpacing(200)
        self.groupbox.setLayout(layout_top)

        layout_grid = QGridLayout()
        layout_grid.addWidget(self.label_project_name, 0, 0)
        layout_grid.addWidget(self.text_project_name, 0, 1, 1, 2)
        layout_grid.addWidget(self.label_location, 1, 0)
        layout_grid.addWidget(self.text_location, 1, 1)
        layout_grid.addWidget(self.button_select_location, 1, 2)
        layout_grid.addWidget(self.label_project_type, 2, 0)
        layout_grid.addWidget(self.combo_project_type, 2, 1, 1, 2)
        layout_grid.addWidget(self.label_information, 3, 0, 1, 3)

        layout = QVBoxLayout()
        layout.addWidget(self.description_label)
        layout.addSpacing(3)
        layout.addWidget(self.groupbox)
        layout.addSpacing(8)
        layout.addLayout(layout_grid)
        layout.addSpacing(8)
        layout.addWidget(self.bbox)
        layout.setSizeConstraint(layout.SetFixedSize)

        self.setLayout(layout)

        # Signals and slots
        self.button_create.clicked.connect(self.create_project)
        self.button_cancel.clicked.connect(self.close)
        self.radio_from_dir.clicked.connect(self.update_location)
        self.radio_new_dir.clicked.connect(self.update_location)
        self.text_project_name.textChanged.connect(self.update_location)

    def select_location(self):
        """Select directory."""
        location = osp.normpath(
            getexistingdirectory(
                self,
                _("Select directory"),
                self.location,
            ))

        if location and location != '.':
            if is_writable(location):
                self.location = location
                self.text_project_name.setText(osp.basename(location))
                self.update_location()

    def update_location(self, text=''):
        """Update text of location and validate it."""
        msg = ''
        path_validation = False
        path = self.location
        name = self.text_project_name.text().strip()

        # Setup
        self.text_project_name.setEnabled(self.radio_new_dir.isChecked())
        self.label_information.setText('')
        self.label_information.hide()

        if name and self.radio_new_dir.isChecked():
            # Allow to create projects only on new directories.
            path = osp.join(self.location, name)
            path_validation = not osp.isdir(path)
            if not path_validation:
                msg = _("This directory already exists!")
        elif self.radio_from_dir.isChecked():
            # Allow to create projects in current directories that are not
            # Spyder projects.
            path = self.location
            path_validation = not osp.isdir(osp.join(path, '.spyproject'))
            if not path_validation:
                msg = _("This directory is already a Spyder project!")

        # Set path in text_location
        self.text_location.setText(path)

        # Validate project name with the method from the currently selected
        # project.
        project_type_id = self.combo_project_type.currentData()
        validate_func = self._project_types[project_type_id].validate_name
        project_name_validation, project_msg = validate_func(path, name)
        if not project_name_validation:
            if msg:
                msg = msg + '\n\n' + project_msg
            else:
                msg = project_msg

        # Set message
        if msg:
            self.label_information.show()
            self.label_information.setText('\n' + msg)

        # Allow to create project if validation was successful
        validated = path_validation and project_name_validation
        self.button_create.setEnabled(validated)

        # Set default state of buttons according to validation
        # Fixes spyder-ide/spyder#16745
        if validated:
            self.button_create.setDefault(True)
            self.button_create.setAutoDefault(True)
        else:
            self.button_cancel.setDefault(True)
            self.button_cancel.setAutoDefault(True)

    def create_project(self):
        """Create project."""
        self.project_data = {
            "root_path": self.text_location.text(),
            "project_type": self.combo_project_type.currentData(),
        }
        self.sig_project_creation_requested.emit(
            self.text_location.text(),
            self.combo_project_type.currentData(),
            [],
        )
        self.accept()
Пример #15
0
class InsertNormalizedConfig(SiriusDialog):
    """Auxiliary window to insert a new normalized config."""

    insertConfig = Signal(float, str, dict)

    def __init__(self, parent, ramp_config):
        """Initialize object."""
        super().__init__(parent)
        self.setObjectName('BOApp')
        self.ramp_config = ramp_config
        self.setWindowTitle('Insert Normalized Configuration')
        self._setupUi()

    def _setupUi(self):
        # selection widgets
        self.rb_interp = QRadioButton('Interpolate')
        self.rb_confsrv = QRadioButton('Take value from Config Server')
        self.rb_create = QRadioButton('Create from template')

        # data widget
        self.config_data = QWidget()
        glay_data = QGridLayout(self.config_data)
        self.le_interp_label = QLineEdit(self)  # interpolate
        self.le_confsrv_name = _ConfigLineEdit(
            parent=self, config_type='bo_normalized')  # from ConfigDB
        self.le_confsrv_name.textChanged.connect(
            self._handleInsertExistingConfig)
        self.le_confsrv_name.setVisible(False)
        self.le_nominal_label = QLineEdit(self)  # from template
        self.le_nominal_label.setVisible(False)
        self.sb_time = QDoubleSpinBoxPlus(self)
        self.sb_time.setMaximum(490)
        self.sb_time.setDecimals(3)
        self.bt_insert = QPushButton('Insert', self)
        self.bt_insert.setAutoDefault(False)
        self.bt_insert.setDefault(False)
        self.bt_insert.clicked.connect(self._emitConfigData)
        self.bt_cancel = QPushButton('Cancel', self)
        self.bt_cancel.setAutoDefault(False)
        self.bt_cancel.setDefault(False)
        self.bt_cancel.clicked.connect(self.close)
        glay_data.addWidget(QLabel('Label: ', self), 0, 0)
        glay_data.addWidget(self.le_interp_label, 0, 1)
        glay_data.addWidget(self.le_confsrv_name, 0, 1)
        glay_data.addWidget(self.le_nominal_label, 0, 1)
        glay_data.addWidget(QLabel('Time [ms]: ', self), 1, 0)
        glay_data.addWidget(self.sb_time, 1, 1)
        glay_data.addWidget(self.bt_cancel, 2, 0)
        glay_data.addWidget(self.bt_insert, 2, 1)

        # connect visibility signals
        self.rb_interp.toggled.connect(self.le_interp_label.setVisible)
        self.rb_interp.setChecked(True)
        self.rb_confsrv.toggled.connect(self.le_confsrv_name.setVisible)
        self.rb_create.toggled.connect(self.le_nominal_label.setVisible)

        # layout
        lay = QVBoxLayout()
        lay.addWidget(QLabel('<h4>Insert a Normalized Configuration</h4>',
                             self),
                      alignment=Qt.AlignCenter)
        lay.addWidget(self.rb_interp)
        lay.addWidget(self.rb_confsrv)
        lay.addWidget(self.rb_create)
        lay.addStretch()
        lay.addWidget(self.config_data)
        self.setLayout(lay)

    @Slot(str)
    def _handleInsertExistingConfig(self, configname):
        try:
            self.norm_config = ramp.BoosterNormalized()
            self.norm_config.name = configname
            self.norm_config.load()
            energy = self.norm_config[ramp.BoosterRamp.PSNAME_DIPOLE_REF]
            time = self.ramp_config.ps_waveform_interp_time(energy)
            self.sb_time.setValue(time)
        except _ConfigDBException as err:
            QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok)

    def _emitConfigData(self):
        time = self.sb_time.value()
        if self.le_interp_label.isVisible():
            label = self.le_interp_label.text()
            psname2strength = dict()
        elif self.le_confsrv_name.isVisible():
            label = self.le_confsrv_name.text()
            psname2strength = {
                _PVName(k).device_name: v
                for k, v, d in self.norm_config.value['pvs']
            }
        elif self.le_nominal_label.isVisible():
            label = self.le_nominal_label.text()
            psname2strength = \
                self.ramp_config.ps_normalized_config_nominal_values
        self.insertConfig.emit(time, label, psname2strength)
        self.close()
Пример #16
0
class DeleteNormalizedConfig(SiriusDialog):
    """Auxiliary window to delete a normalized config."""

    deleteConfig = Signal(float)

    def __init__(self, parent, table_map, selected_row):
        """Initialize object."""
        super().__init__(parent)
        self.setObjectName('BOApp')
        self.setWindowTitle('Delete Normalized Configuration')
        self.table_map = table_map
        self.selected_row = selected_row
        self._setupUi()

    def _setupUi(self):
        self.sb_confignumber = QSpinBoxPlus(self)
        self.sb_confignumber.setMinimum(1)
        self.sb_confignumber.setMaximum(max(self.table_map['rows'].keys()) + 1)
        self.sb_confignumber.setStyleSheet("""max-width:5em;""")
        self.sb_confignumber.valueChanged.connect(self._searchConfigByIndex)

        self.bt_delete = QPushButton('Delete', self)
        self.bt_delete.setAutoDefault(False)
        self.bt_delete.setDefault(False)
        self.bt_delete.clicked.connect(self._emitConfigData)

        self.l_configid = QLabel('', self)
        self.l_configid.setSizePolicy(QSzPlcy.MinimumExpanding,
                                      QSzPlcy.Expanding)
        self.sb_confignumber.setValue(self.selected_row + 1)
        self._searchConfigByIndex(self.selected_row + 1)

        self.bt_cancel = QPushButton('Cancel', self)
        self.bt_cancel.setAutoDefault(False)
        self.bt_cancel.setDefault(False)
        self.bt_cancel.clicked.connect(self.close)

        glay = QGridLayout()
        glay.setVerticalSpacing(15)
        glay.setHorizontalSpacing(10)
        label = QLabel('<h4>Delete a Normalized Configuration</h4>', self)
        label.setAlignment(Qt.AlignCenter)
        glay.addWidget(label, 0, 0, 1, 2)
        glay.addWidget(self.sb_confignumber, 2, 0)
        glay.addWidget(self.l_configid, 2, 1)
        glay.addWidget(self.bt_cancel, 4, 0)
        glay.addWidget(self.bt_delete, 4, 1)
        self.setLayout(glay)

    @Slot(int)
    def _searchConfigByIndex(self, config_idx):
        label = self.table_map['rows'][config_idx - 1]
        self.l_configid.setText(str(label))
        if label in ['Injection', 'Ejection']:
            self.bt_delete.setEnabled(False)
        else:
            self.bt_delete.setEnabled(True)

    def _emitConfigData(self):
        self.deleteConfig.emit(float(self.l_configid.text()))
        self.close()
Пример #17
0
    def __init__(self,
                 ispec,
                 abs_sys,
                 parent=None,
                 llist=None,
                 norm=True,
                 vmnx=[-300., 300.] * u.km / u.s,
                 outfil=None):
        """
        spec : Filename or Spectrum1D
        abs_sys : AbsSystem
          Absorption system class
        Norm : bool, optional
          Normalized spectrum?
        """
        from linetools.guis import spec_widgets as ltgs
        super(XAbsSysGui, self).__init__(parent)

        # Initialize
        self.abs_sys = abs_sys
        self.z = self.abs_sys.zabs
        abs_lines = abs_sys.list_of_abslines()
        self.vmnx = vmnx
        if outfil is None:
            self.outfil = 'tmp_abskin.json'
            warnings.warn(
                "Outfil not specified.  Using {:s} as the default".format(
                    self.outfil))
        else:
            self.outfil = outfil
        self.norm = norm

        # Grab the pieces and tie together
        newfont = QtGui.QFont("Times", 10, QtGui.QFont.Bold)
        sys_label = QLabel('Name: \n {:s}'.format(abs_sys.name))
        sys_label.setFont(newfont)
        self.vplt_widg = ltgs.VelPlotWidget(ispec,
                                            self.z,
                                            abs_lines=abs_lines,
                                            llist=llist,
                                            vmnx=self.vmnx,
                                            norm=self.norm)
        self.pltline_widg = ltgl.PlotLinesWidget(
            init_llist=self.vplt_widg.llist, init_z=self.z, edit_z=False)
        #self.pltline_widg.spec_widg = self.vplt_widg

        self.slines = ltgl.SelectedLinesWidget(
            self.vplt_widg.llist[self.vplt_widg.llist['List']],
            init_select=self.vplt_widg.llist['show_line'],
            plot_widget=self.vplt_widg)

        # Connections
        self.pltline_widg.llist_widget.currentItemChanged.connect(
            self.on_llist_change)
        #self.connect(self.pltline_widg.zbox, QtCore.SIGNAL('editingFinished ()'), self.setz)
        self.vplt_widg.canvas.mpl_connect('key_press_event', self.on_key)

        # Outfil
        wbtn = QPushButton(self)
        wbtn.setText('Write')
        wbtn.setAutoDefault(False)
        wbtn.clicked.connect(self.write_out)
        self.out_box = QLineEdit()
        self.out_box.setText(self.outfil)
        self.out_box.textChanged[str].connect(self.set_outfil)
        #self.connect(self.out_box, QtCore.SIGNAL('editingFinished ()'), self.set_outfil)

        #QtCore.pyqtRemoveInputHook()
        #pdb.set_trace()
        #QtCore.pyqtRestoreInputHook()

        # Quit
        buttons = QWidget()
        wqbtn = QPushButton(self)
        wqbtn.setText('Write+Quit')
        wqbtn.setAutoDefault(False)
        wqbtn.clicked.connect(self.write_quit)
        qbtn = QPushButton(self)
        qbtn.setText('Quit')
        qbtn.setAutoDefault(False)
        qbtn.clicked.connect(self.quit)

        # Sizes
        lines_widg = QWidget()
        lines_widg.setMaximumWidth(300)
        lines_widg.setMinimumWidth(200)

        # Layout
        vbox = QVBoxLayout()
        vbox.addWidget(sys_label)
        vbox.addWidget(self.pltline_widg)
        vbox.addWidget(self.slines)
        vbox.addWidget(wbtn)
        vbox.addWidget(self.out_box)
        # Write/Quit buttons
        hbox1 = QHBoxLayout()
        hbox1.addWidget(wqbtn)
        hbox1.addWidget(qbtn)
        buttons.setLayout(hbox1)
        #
        vbox.addWidget(buttons)
        lines_widg.setLayout(vbox)

        hbox = QHBoxLayout()
        hbox.addWidget(self.vplt_widg)
        hbox.addWidget(lines_widg)

        self.setLayout(hbox)
        # Initial draw
        self.vplt_widg.on_draw()
Пример #18
0
class DownloadBibleMp3Dialog(QDialog):
    def __init__(self, parent):
        super().__init__()

        self.bibles = {
            "BBE (British accent)":
            ("BBE", "otseng/UniqueBible_MP3_BBE_british", "british"),
            "KJV (American accent)":
            ("KJV", "otseng/UniqueBible_MP3_KJV", "default"),
            "KJV (American soft music)":
            ("KJV", "otseng/UniqueBible_MP3_KJV_soft_music", "soft-music"),
            "NHEB (Indian accent)":
            ("NHEB", "otseng/UniqueBible_MP3_NHEB_indian", "indian"),
            "WEB (American accent)": ("WEB", "otseng/UniqueBible_MP3_WEB",
                                      "default"),
            "CUV (Chinese)": ("CUV", "otseng/UniqueBible_MP3_CUV", "default"),
            "HHBD (Hindi)": ("HHBD", "otseng/UniqueBible_MP3_HHBD", "default"),
            "RVA (Spanish)": ("RVA", "otseng/UniqueBible_MP3_RVA", "default"),
            "TR (Modern Greek)": ("TR", "otseng/UniqueBible_MP3_TR", "modern"),
        }
        self.parent = parent
        self.setWindowTitle(config.thisTranslation["gitHubBibleMp3Files"])
        self.setMinimumSize(150, 450)
        self.selectedRendition = None
        self.selectedText = None
        self.selectedRepo = None
        self.selectedDirectory = None
        self.settingBibles = False
        self.thread = None
        self.setupUI()

    def setupUI(self):
        mainLayout = QVBoxLayout()

        title = QLabel(config.thisTranslation["gitHubBibleMp3Files"])
        mainLayout.addWidget(title)

        self.versionsLayout = QVBoxLayout()
        self.renditionsList = QListWidget()
        self.renditionsList.itemClicked.connect(self.selectItem)
        for rendition in self.bibles.keys():
            self.renditionsList.addItem(rendition)
        self.renditionsList.setMaximumHeight(100)
        self.versionsLayout.addWidget(self.renditionsList)
        mainLayout.addLayout(self.versionsLayout)

        self.downloadTable = QTableView()
        self.downloadTable.setEnabled(False)
        self.downloadTable.setFocusPolicy(Qt.StrongFocus)
        self.downloadTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.downloadTable.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.downloadTable)
        self.downloadTable.setModel(self.dataViewModel)
        mainLayout.addWidget(self.downloadTable)

        buttonsLayout = QHBoxLayout()
        selectAllButton = QPushButton(config.thisTranslation["selectAll"])
        selectAllButton.setFocusPolicy(Qt.StrongFocus)
        selectAllButton.clicked.connect(self.selectAll)
        buttonsLayout.addWidget(selectAllButton)
        selectNoneButton = QPushButton(config.thisTranslation["selectNone"])
        selectNoneButton.setFocusPolicy(Qt.StrongFocus)
        selectNoneButton.clicked.connect(self.selectNone)
        buttonsLayout.addWidget(selectNoneButton)
        otButton = QPushButton("1-39")
        otButton.setFocusPolicy(Qt.StrongFocus)
        otButton.clicked.connect(self.selectOT)
        buttonsLayout.addWidget(otButton)
        ntButton = QPushButton("40-66")
        ntButton.setFocusPolicy(Qt.StrongFocus)
        ntButton.clicked.connect(self.selectNT)
        buttonsLayout.addWidget(ntButton)
        # buttonsLayout.addStretch()
        mainLayout.addLayout(buttonsLayout)

        self.downloadButton = QPushButton(config.thisTranslation["download"])
        self.downloadButton.setFocusPolicy(Qt.StrongFocus)
        self.downloadButton.setAutoDefault(True)
        self.downloadButton.setFocus()
        self.downloadButton.clicked.connect(self.download)
        mainLayout.addWidget(self.downloadButton)

        self.status = QLabel("")
        mainLayout.addWidget(self.status)

        buttonLayout = QHBoxLayout()
        self.closeButton = QPushButton(config.thisTranslation["close"])
        self.closeButton.setFocusPolicy(Qt.StrongFocus)
        self.closeButton.clicked.connect(self.closeDialog)
        buttonLayout.addWidget(self.closeButton)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

        self.renditionsList.item(0).setSelected(True)
        bible = self.renditionsList.item(0).text()
        self.selectRendition(bible)

        self.downloadButton.setDefault(True)
        QTimer.singleShot(0, self.downloadButton.setFocus)

    def selectItem(self, item):
        self.selectRendition(item.text())

    def selectRendition(self, rendition):
        from util.GithubUtil import GithubUtil

        self.selectedRendition = rendition
        self.downloadTable.setEnabled(True)
        self.selectedText, self.selectedRepo, self.selectedDirectory = self.bibles[
            self.selectedRendition]
        self.github = GithubUtil(self.selectedRepo)
        self.repoData = self.github.getRepoData()
        self.settingBibles = True
        self.dataViewModel.clear()
        rowCount = 0
        for file in self.repoData.keys():
            if len(str(file)) > 3:
                engFullBookName = file[3:]
            else:
                engFullBookName = BibleBooks().eng[str(int(file))][1]
            item = QStandardItem(file[:3].strip())
            folder = os.path.join("audio", "bibles", self.selectedText,
                                  self.selectedDirectory, file)
            folderWithName = os.path.join("audio", "bibles", self.selectedText,
                                          self.selectedDirectory,
                                          file + " " + engFullBookName)
            if os.path.exists(folder) or os.path.exists(folderWithName):
                item.setCheckable(False)
                item.setCheckState(Qt.Unchecked)
                item.setEnabled(False)
            else:
                item.setCheckable(True)
                item.setCheckState(Qt.Checked)
                item.setEnabled(True)
            self.dataViewModel.setItem(rowCount, 0, item)
            item = QStandardItem(engFullBookName)
            self.dataViewModel.setItem(rowCount, 1, item)
            if os.path.exists(folder) or os.path.exists(folderWithName):
                item = QStandardItem("Installed")
                self.dataViewModel.setItem(rowCount, 2, item)
            else:
                item = QStandardItem("")
                self.dataViewModel.setItem(rowCount, 2, item)
            rowCount += 1
        self.dataViewModel.setHorizontalHeaderLabels([
            config.thisTranslation["menu_book"],
            config.thisTranslation["name"], ""
        ])
        self.downloadTable.setColumnWidth(0, 90)
        self.downloadTable.setColumnWidth(1, 125)
        self.downloadTable.setColumnWidth(2, 125)
        # self.downloadTable.resizeColumnsToContents()
        self.settingBibles = False

    def selectAll(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            if item.isEnabled():
                item.setCheckState(Qt.Checked)

    def selectNone(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            item.setCheckState(Qt.Unchecked)

    def selectOT(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            bookNum = int(item.text())
            if bookNum <= 39:
                if item.isEnabled():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Unchecked)

    def selectNT(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            bookNum = int(item.text())
            if bookNum >= 40:
                if item.isEnabled():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Unchecked)

    def download(self):
        self.downloadButton.setEnabled(False)
        self.setStatus(config.thisTranslation["message_installing"])
        self.closeButton.setEnabled(False)
        folder = os.path.join("audio", "bibles")
        if not os.path.exists(folder):
            os.mkdir(folder)
        folder = os.path.join("audio", "bibles", self.selectedText)
        if not os.path.exists(folder):
            os.mkdir(folder)
        folder = os.path.join("audio", "bibles", self.selectedText,
                              self.selectedDirectory)
        if not os.path.exists(folder):
            os.mkdir(folder)
        self.thread = QThread()
        self.worker = DownloadFromGitHub(self.github, self.repoData,
                                         self.dataViewModel, self.selectedText,
                                         self.selectedDirectory)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.worker.deleteLater)
        self.worker.finished.connect(self.finishedDownloading)
        self.worker.progress.connect(self.setStatus)
        self.thread.start()

    def finishedDownloading(self, count):
        self.selectRendition(self.selectedRendition)
        self.setStatus("")
        self.downloadButton.setEnabled(True)
        self.closeButton.setEnabled(True)
        if count > 0:
            self.parent.displayMessage(
                config.thisTranslation["message_installed"])

    def setStatus(self, message):
        self.status.setText(message)
        QApplication.processEvents()

    def closeDialog(self):
        if self.thread:
            if self.thread.isRunning():
                self.thread.quit()
        self.close()
Пример #19
0
class ProjectDialog(QDialog):
    """Project creation dialog."""

    sig_project_creation_requested = Signal(str, str, object)
    """
    This signal is emitted to request the Projects plugin the creation of a
    project.

    Parameters
    ----------
    project_path: str
        Location of project.
    project_type: str	
        Type of project as defined by project types.	
    project_packages: object	
        Package to install. Currently not in use.	
    """

    def __init__(self, parent, project_types):
        """Project creation dialog."""
        super(ProjectDialog, self).__init__(parent=parent)
        self.plugin = parent
        self._project_types = project_types
        self.project_data = {}

        # Variables
        current_python_version = '.'.join([to_text_string(sys.version_info[0]),
                                           to_text_string(sys.version_info[1])])
        python_versions = ['2.7', '3.4', '3.5']
        if current_python_version not in python_versions:
            python_versions.append(current_python_version)
            python_versions = sorted(python_versions)

        self.project_name = None
        self.location = get_home_dir()

        # Widgets
        self.groupbox = QGroupBox()
        self.radio_new_dir = QRadioButton(_("New directory"))
        self.radio_from_dir = QRadioButton(_("Existing directory"))

        self.label_project_name = QLabel(_('Project name'))
        self.label_location = QLabel(_('Location'))
        self.label_project_type = QLabel(_('Project type'))
        self.label_python_version = QLabel(_('Python version'))

        self.text_project_name = QLineEdit()
        self.text_location = QLineEdit(get_home_dir())
        self.combo_project_type = QComboBox()
        self.combo_python_version = QComboBox()

        self.label_information = QLabel("")

        self.button_select_location = QToolButton()
        self.button_cancel = QPushButton(_('Cancel'))
        self.button_create = QPushButton(_('Create'))

        self.bbox = QDialogButtonBox(Qt.Horizontal)
        self.bbox.addButton(self.button_cancel, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.button_create, QDialogButtonBox.ActionRole)

        # Widget setup
        self.combo_python_version.addItems(python_versions)
        self.radio_new_dir.setChecked(True)
        self.text_location.setEnabled(True)
        self.text_location.setReadOnly(True)
        self.button_select_location.setIcon(get_std_icon('DirOpenIcon'))
        self.button_cancel.setDefault(True)
        self.button_cancel.setAutoDefault(True)
        self.button_create.setEnabled(False)
        for (id_, name) in [(pt_id, pt.get_name()) for pt_id, pt
                            in project_types.items()]:
            self.combo_project_type.addItem(name, id_)

        self.combo_python_version.setCurrentIndex(
            python_versions.index(current_python_version))
        self.setWindowTitle(_('Create new project'))
        self.setFixedWidth(500)
        self.label_python_version.setVisible(False)
        self.combo_python_version.setVisible(False)

        # Layouts
        layout_top = QHBoxLayout()
        layout_top.addWidget(self.radio_new_dir)
        layout_top.addWidget(self.radio_from_dir)
        layout_top.addStretch(1)
        self.groupbox.setLayout(layout_top)

        layout_grid = QGridLayout()
        layout_grid.addWidget(self.label_project_name, 0, 0)
        layout_grid.addWidget(self.text_project_name, 0, 1, 1, 2)
        layout_grid.addWidget(self.label_location, 1, 0)
        layout_grid.addWidget(self.text_location, 1, 1)
        layout_grid.addWidget(self.button_select_location, 1, 2)
        layout_grid.addWidget(self.label_project_type, 2, 0)
        layout_grid.addWidget(self.combo_project_type, 2, 1, 1, 2)
        layout_grid.addWidget(self.label_python_version, 3, 0)
        layout_grid.addWidget(self.combo_python_version, 3, 1, 1, 2)
        layout_grid.addWidget(self.label_information, 4, 0, 1, 3)

        layout = QVBoxLayout()
        layout.addWidget(self.groupbox)
        layout.addSpacing(10)
        layout.addLayout(layout_grid)
        layout.addStretch()
        layout.addSpacing(20)
        layout.addWidget(self.bbox)

        self.setLayout(layout)

        # Signals and slots
        self.button_select_location.clicked.connect(self.select_location)
        self.button_create.clicked.connect(self.create_project)
        self.button_cancel.clicked.connect(self.close)
        self.radio_from_dir.clicked.connect(self.update_location)
        self.radio_new_dir.clicked.connect(self.update_location)
        self.text_project_name.textChanged.connect(self.update_location)

    def select_location(self):
        """Select directory."""
        location = osp.normpath(
            getexistingdirectory(
                self,
                _("Select directory"),
                self.location,
            )
        )

        if location:
            if is_writable(location):
                self.location = location
                self.update_location()

    def update_location(self, text=''):
        """Update text of location."""
        self.text_project_name.setEnabled(self.radio_new_dir.isChecked())
        name = self.text_project_name.text().strip()

        if name and self.radio_new_dir.isChecked():
            path = osp.join(self.location, name)
            self.button_create.setDisabled(os.path.isdir(path))
        elif self.radio_from_dir.isChecked():
            self.button_create.setEnabled(True)
            path = self.location
        else:
            self.button_create.setEnabled(False)
            path = self.location

        self.text_location.setText(path)

        # Validate name with the method from the currently selected project
        project_type_id = self.combo_project_type.currentData()
        validate_func = self._project_types[project_type_id].validate_name
        validated, msg = validate_func(path, name)
        msg = "" if validated else msg
        self.label_information.setText(msg)
        self.button_create.setEnabled(validated)

    def create_project(self):
        """Create project."""
        self.project_data = {
            "root_path": self.text_location.text(),
            "project_type": self.combo_project_type.currentData(),
        }
        self.sig_project_creation_requested.emit(
            self.text_location.text(),
            self.combo_project_type.currentData(),
            [],
        )
        self.accept()
Пример #20
0
    def __init__(self, parent):
        super(KiteIntegrationInfo, self).__init__(parent)
        # Images
        images_layout = QHBoxLayout()
        icon_filename = 'kite_completions.png'
        image_path = get_image_path(icon_filename)
        image = QPixmap(image_path)
        image_label = QLabel()
        screen = QApplication.primaryScreen()
        image_label = QLabel()
        image_height = int(image.height() * self.ICON_SCALE_FACTOR)
        image_width = int(image.width() * self.ICON_SCALE_FACTOR)
        image = image.scaled(image_width, image_height, Qt.KeepAspectRatio,
                             Qt.SmoothTransformation)
        image_label.setPixmap(image)

        images_layout.addStretch()
        images_layout.addWidget(image_label)
        images_layout.addStretch()

        ilayout = QHBoxLayout()
        ilayout.addLayout(images_layout)

        # Label
        integration_label_title = QLabel(
            "Get better code completions in Spyder")
        integration_label_title.setStyleSheet(
            f"font-size: {self.TITLE_FONT_SIZE}")
        integration_label_title.setWordWrap(True)
        integration_label = QLabel(
            _("Now Spyder can use Kite to provide better code "
              "completions for key packages in the scientific Python "
              "Ecosystem. Install Kite for a better editor experience in "
              "Spyder. <br><br>Kite is free to use but is not open "
              "source. <a href=\"{kite_url}\">Learn more about Kite </a>").
            format(kite_url=KITE_SPYDER_URL))
        integration_label.setStyleSheet(f"font-size: {self.CONTENT_FONT_SIZE}")
        integration_label.setOpenExternalLinks(True)
        integration_label.setWordWrap(True)
        integration_label.setFixedWidth(360)
        label_layout = QVBoxLayout()
        label_layout.addWidget(integration_label_title)
        label_layout.addWidget(integration_label)

        # Buttons
        buttons_layout = QHBoxLayout()
        install_button = QPushButton(_('Install Kite'))
        install_button.setAutoDefault(False)
        install_button.setStyleSheet("background-color: #3775A9;"
                                     f"font-size: {self.BUTTONS_FONT_SIZE};"
                                     f"padding: {self.BUTTONS_PADDING}")
        dismiss_button = QPushButton(_('Dismiss'))
        dismiss_button.setAutoDefault(False)
        dismiss_button.setStyleSheet("background-color: #60798B;"
                                     f"font-size: {self.BUTTONS_FONT_SIZE};"
                                     f"padding: {self.BUTTONS_PADDING}")
        buttons_layout.addStretch()
        buttons_layout.addWidget(install_button)
        if not MAC:
            buttons_layout.addSpacing(10)
        buttons_layout.addWidget(dismiss_button)

        # Buttons with label
        vertical_layout = QVBoxLayout()
        if not MAC:
            vertical_layout.addStretch()
            vertical_layout.addLayout(label_layout)
            vertical_layout.addSpacing(20)
            vertical_layout.addLayout(buttons_layout)
            vertical_layout.addStretch()
        else:
            vertical_layout.addLayout(label_layout)
            vertical_layout.addLayout(buttons_layout)

        general_layout = QHBoxLayout()
        general_layout.addStretch()
        general_layout.addLayout(ilayout)
        general_layout.addSpacing(15)
        general_layout.addLayout(vertical_layout)
        general_layout.addStretch()

        self.setLayout(general_layout)

        # Signals
        install_button.clicked.connect(self.sig_install_button_clicked)
        dismiss_button.clicked.connect(self.sig_dismiss_button_clicked)

        if is_dark_interface():
            self.setStyleSheet("background-color: #262E38")
        self.setContentsMargins(18, 40, 18, 40)
        if not MAC:
            self.setFixedSize(800, 350)
Пример #21
0
class TextEditor(BaseDialog):
    """Array Editor Dialog"""
    def __init__(self, text, title='', font=None, parent=None, readonly=False):
        QDialog.__init__(self, parent)

        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)

        self.text = None
        self.btn_save_and_close = None

        # Display text as unicode if it comes as bytes, so users see
        # its right representation
        if is_binary_string(text):
            self.is_binary = True
            text = to_text_string(text, 'utf8')
        else:
            self.is_binary = False

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        # Text edit
        self.edit = QTextEdit(parent)
        self.edit.setReadOnly(readonly)
        self.edit.textChanged.connect(self.text_changed)
        self.edit.setPlainText(text)
        if font is None:
            font = get_font()
        self.edit.setFont(font)
        self.layout.addWidget(self.edit)

        # Buttons configuration
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        if not readonly:
            self.btn_save_and_close = QPushButton(_('Save and Close'))
            self.btn_save_and_close.setDisabled(True)
            self.btn_save_and_close.clicked.connect(self.accept)
            btn_layout.addWidget(self.btn_save_and_close)

        self.btn_close = QPushButton(_('Close'))
        self.btn_close.setAutoDefault(True)
        self.btn_close.setDefault(True)
        self.btn_close.clicked.connect(self.reject)
        btn_layout.addWidget(self.btn_close)

        self.layout.addLayout(btn_layout)

        # Make the dialog act as a window
        self.setWindowFlags(Qt.Window)

        self.setWindowIcon(ima.icon('edit'))
        if title:
            try:
                unicode_title = to_text_string(title)
            except UnicodeEncodeError:
                unicode_title = u''
        else:
            unicode_title = u''

        self.setWindowTitle(_("Text editor") + \
                            u"%s" % (u" - " + unicode_title
                                     if unicode_title else u""))

    @Slot()
    def text_changed(self):
        """Text has changed"""
        # Save text as bytes, if it was initially bytes
        if self.is_binary:
            self.text = to_binary_string(self.edit.toPlainText(), 'utf8')
        else:
            self.text = to_text_string(self.edit.toPlainText())
        if self.btn_save_and_close:
            self.btn_save_and_close.setEnabled(True)
            self.btn_save_and_close.setAutoDefault(True)
            self.btn_save_and_close.setDefault(True)

    def get_value(self):
        """Return modified text"""
        # It is import to avoid accessing Qt C++ object as it has probably
        # already been destroyed, due to the Qt.WA_DeleteOnClose attribute
        return self.text

    def setup_and_check(self, value):
        """Verify if TextEditor is able to display strings passed to it."""
        try:
            to_text_string(value, 'utf8')
            return True
        except:
            return False
Пример #22
0
class ProjectDialog(QDialog):
    """Project creation dialog."""

    # path, type, packages
    sig_project_creation_requested = Signal(object, object, object)

    def __init__(self, parent):
        """Project creation dialog."""
        super(ProjectDialog, self).__init__(parent=parent)

        # Variables
        current_python_version = '.'.join([
            to_text_string(sys.version_info[0]),
            to_text_string(sys.version_info[1])
        ])
        python_versions = ['2.7', '3.4', '3.5']
        if current_python_version not in python_versions:
            python_versions.append(current_python_version)
            python_versions = sorted(python_versions)

        self.project_name = None
        self.location = get_home_dir()

        # Widgets
        self.groupbox = QGroupBox()
        self.radio_new_dir = QRadioButton(_("New directory"))
        self.radio_from_dir = QRadioButton(_("Existing directory"))

        self.label_project_name = QLabel(_('Project name'))
        self.label_location = QLabel(_('Location'))
        self.label_project_type = QLabel(_('Project type'))
        self.label_python_version = QLabel(_('Python version'))

        self.text_project_name = QLineEdit()
        self.text_location = QLineEdit(get_home_dir())
        self.combo_project_type = QComboBox()
        self.combo_python_version = QComboBox()

        self.button_select_location = QToolButton()
        self.button_cancel = QPushButton(_('Cancel'))
        self.button_create = QPushButton(_('Create'))

        self.bbox = QDialogButtonBox(Qt.Horizontal)
        self.bbox.addButton(self.button_cancel, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.button_create, QDialogButtonBox.ActionRole)

        # Widget setup
        self.combo_python_version.addItems(python_versions)
        self.radio_new_dir.setChecked(True)
        self.text_location.setEnabled(True)
        self.text_location.setReadOnly(True)
        self.button_select_location.setIcon(get_std_icon('DirOpenIcon'))
        self.button_cancel.setDefault(True)
        self.button_cancel.setAutoDefault(True)
        self.button_create.setEnabled(False)
        self.combo_project_type.addItems(self._get_project_types())
        self.combo_python_version.setCurrentIndex(
            python_versions.index(current_python_version))
        self.setWindowTitle(_('Create new project'))
        self.setFixedWidth(500)
        self.label_python_version.setVisible(False)
        self.combo_python_version.setVisible(False)

        # Layouts
        layout_top = QHBoxLayout()
        layout_top.addWidget(self.radio_new_dir)
        layout_top.addWidget(self.radio_from_dir)
        layout_top.addStretch(1)
        self.groupbox.setLayout(layout_top)

        layout_grid = QGridLayout()
        layout_grid.addWidget(self.label_project_name, 0, 0)
        layout_grid.addWidget(self.text_project_name, 0, 1, 1, 2)
        layout_grid.addWidget(self.label_location, 1, 0)
        layout_grid.addWidget(self.text_location, 1, 1)
        layout_grid.addWidget(self.button_select_location, 1, 2)
        layout_grid.addWidget(self.label_project_type, 2, 0)
        layout_grid.addWidget(self.combo_project_type, 2, 1, 1, 2)
        layout_grid.addWidget(self.label_python_version, 3, 0)
        layout_grid.addWidget(self.combo_python_version, 3, 1, 1, 2)

        layout = QVBoxLayout()
        layout.addWidget(self.groupbox)
        layout.addSpacing(10)
        layout.addLayout(layout_grid)
        layout.addStretch()
        layout.addSpacing(20)
        layout.addWidget(self.bbox)

        self.setLayout(layout)

        # Signals and slots
        self.button_select_location.clicked.connect(self.select_location)
        self.button_create.clicked.connect(self.create_project)
        self.button_cancel.clicked.connect(self.close)
        self.radio_from_dir.clicked.connect(self.update_location)
        self.radio_new_dir.clicked.connect(self.update_location)
        self.text_project_name.textChanged.connect(self.update_location)

    def _get_project_types(self):
        """Get all available project types."""
        project_types = get_available_project_types()
        projects = []

        for project in project_types:
            projects.append(project.PROJECT_TYPE_NAME)

        return projects

    def select_location(self):
        """Select directory."""
        location = getexistingdirectory(self, _("Select directory"),
                                        self.location)
        if location:
            if is_writable(location):
                self.location = location
                self.update_location()

    def update_location(self, text=''):
        """Update text of location."""
        self.text_project_name.setEnabled(self.radio_new_dir.isChecked())
        name = self.text_project_name.text().strip()

        if name and self.radio_new_dir.isChecked():
            path = osp.join(self.location, name)
            self.button_create.setDisabled(os.path.isdir(path))
        elif self.radio_from_dir.isChecked():
            self.button_create.setEnabled(True)
            path = self.location
        else:
            self.button_create.setEnabled(False)
            path = self.location

        self.text_location.setText(path)

    def create_project(self):
        """Create project."""
        packages = [
            'python={0}'.format(self.combo_python_version.currentText())
        ]
        self.sig_project_creation_requested.emit(
            self.text_location.text(), self.combo_project_type.currentText(),
            packages)
        self.accept()
Пример #23
0
class ProjectDialog(QDialog):
    """Project creation dialog."""

    # path, type, packages
    sig_project_creation_requested = Signal(object, object, object)

    def __init__(self, parent):
        """Project creation dialog."""
        super(ProjectDialog, self).__init__(parent=parent)

        # Variables
        current_python_version = '.'.join([to_text_string(sys.version_info[0]),
                                           to_text_string(sys.version_info[1])])
        python_versions = ['2.7', '3.4', '3.5']
        if current_python_version not in python_versions:
            python_versions.append(current_python_version)
            python_versions = sorted(python_versions)

        self.project_name = None
        self.location = get_home_dir()

        # Widgets
        self.groupbox = QGroupBox()
        self.radio_new_dir = QRadioButton(_("New directory"))
        self.radio_from_dir = QRadioButton(_("Existing directory"))

        self.label_project_name = QLabel(_('Project name'))
        self.label_location = QLabel(_('Location'))
        self.label_project_type = QLabel(_('Project type'))
        self.label_python_version = QLabel(_('Python version'))

        self.text_project_name = QLineEdit()
        self.text_location = QLineEdit(get_home_dir())
        self.combo_project_type = QComboBox()
        self.combo_python_version = QComboBox()

        self.button_select_location = QToolButton()
        self.button_cancel = QPushButton(_('Cancel'))
        self.button_create = QPushButton(_('Create'))

        self.bbox = QDialogButtonBox(Qt.Horizontal)
        self.bbox.addButton(self.button_cancel, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.button_create, QDialogButtonBox.ActionRole)

        # Widget setup
        self.combo_python_version.addItems(python_versions)
        self.radio_new_dir.setChecked(True)
        self.text_location.setEnabled(True)
        self.text_location.setReadOnly(True)
        self.button_select_location.setIcon(get_std_icon('DirOpenIcon'))
        self.button_cancel.setDefault(True)
        self.button_cancel.setAutoDefault(True)
        self.button_create.setEnabled(False)
        self.combo_project_type.addItems(self._get_project_types())
        self.combo_python_version.setCurrentIndex(
            python_versions.index(current_python_version))
        self.setWindowTitle(_('Create new project'))
        self.setFixedWidth(500)
        self.label_python_version.setVisible(False)
        self.combo_python_version.setVisible(False)

        # Layouts        
        layout_top = QHBoxLayout()
        layout_top.addWidget(self.radio_new_dir)
        layout_top.addWidget(self.radio_from_dir)
        layout_top.addStretch(1)
        self.groupbox.setLayout(layout_top)

        layout_grid = QGridLayout()
        layout_grid.addWidget(self.label_project_name, 0, 0)
        layout_grid.addWidget(self.text_project_name, 0, 1, 1, 2)
        layout_grid.addWidget(self.label_location, 1, 0)
        layout_grid.addWidget(self.text_location, 1, 1)
        layout_grid.addWidget(self.button_select_location, 1, 2)
        layout_grid.addWidget(self.label_project_type, 2, 0)
        layout_grid.addWidget(self.combo_project_type, 2, 1, 1, 2)
        layout_grid.addWidget(self.label_python_version, 3, 0)
        layout_grid.addWidget(self.combo_python_version, 3, 1, 1, 2)

        layout = QVBoxLayout()
        layout.addWidget(self.groupbox)
        layout.addSpacing(10)
        layout.addLayout(layout_grid)
        layout.addStretch()
        layout.addSpacing(20)
        layout.addWidget(self.bbox)

        self.setLayout(layout)

        # Signals and slots
        self.button_select_location.clicked.connect(self.select_location)
        self.button_create.clicked.connect(self.create_project)
        self.button_cancel.clicked.connect(self.close)
        self.radio_from_dir.clicked.connect(self.update_location)
        self.radio_new_dir.clicked.connect(self.update_location)
        self.text_project_name.textChanged.connect(self.update_location)

    def _get_project_types(self):
        """Get all available project types."""
        project_types = get_available_project_types()
        projects = []

        for project in project_types:
            projects.append(project.PROJECT_TYPE_NAME)

        return projects

    def select_location(self):
        """Select directory."""
        location = osp.normpath(getexistingdirectory(self,
                                                     _("Select directory"),
                                                     self.location))

        if location:
            if is_writable(location):
                self.location = location
                self.update_location()

    def update_location(self, text=''):
        """Update text of location."""
        self.text_project_name.setEnabled(self.radio_new_dir.isChecked())
        name = self.text_project_name.text().strip()

        if name and self.radio_new_dir.isChecked():
            path = osp.join(self.location, name)
            self.button_create.setDisabled(os.path.isdir(path))
        elif self.radio_from_dir.isChecked():
            self.button_create.setEnabled(True)
            path = self.location
        else:
            self.button_create.setEnabled(False)
            path = self.location
        
        self.text_location.setText(path)
        
    def create_project(self):
        """Create project."""
        packages = ['python={0}'.format(self.combo_python_version.currentText())]
        self.sig_project_creation_requested.emit(
            self.text_location.text(),
            self.combo_project_type.currentText(),
            packages)
        self.accept()
Пример #24
0
class DataFrameEditor(QDialog):
    """
    Dialog for displaying and editing DataFrame and related objects.

    Signals
    -------
    sig_option_changed(str, object): Raised if an option is changed.
       Arguments are name of option and its new value.
    """
    sig_option_changed = Signal(str, object)

    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.is_series = False
        self.layout = None

    def setup_and_check(self, data, title=''):
        """
        Setup DataFrameEditor:
        return False if data is not supported, True otherwise.
        Supported types for data are DataFrame, Series and DatetimeIndex.
        """
        self.layout = QGridLayout()
        self.setLayout(self.layout)
        self.setWindowIcon(ima.icon('arredit'))
        if title:
            title = to_text_string(title) + " - %s" % data.__class__.__name__
        else:
            title = _("%s editor") % data.__class__.__name__
        if isinstance(data, Series):
            self.is_series = True
            data = data.to_frame()
        elif isinstance(data, DatetimeIndex):
            data = DataFrame(data)

        self.setWindowTitle(title)
        self.resize(600, 500)

        self.dataModel = DataFrameModel(data, parent=self)
        self.dataModel.dataChanged.connect(self.save_and_close_enable)
        self.dataTable = DataFrameView(self, self.dataModel)

        self.layout.addWidget(self.dataTable)
        self.setLayout(self.layout)
        self.setMinimumSize(400, 300)
        # Make the dialog act as a window
        self.setWindowFlags(Qt.Window)
        btn_layout = QHBoxLayout()

        btn = QPushButton(_("Format"))
        # disable format button for int type
        btn_layout.addWidget(btn)
        btn.clicked.connect(self.change_format)
        btn = QPushButton(_('Resize'))
        btn_layout.addWidget(btn)
        btn.clicked.connect(self.resize_to_contents)

        bgcolor = QCheckBox(_('Background color'))
        bgcolor.setChecked(self.dataModel.bgcolor_enabled)
        bgcolor.setEnabled(self.dataModel.bgcolor_enabled)
        bgcolor.stateChanged.connect(self.change_bgcolor_enable)
        btn_layout.addWidget(bgcolor)

        self.bgcolor_global = QCheckBox(_('Column min/max'))
        self.bgcolor_global.setChecked(self.dataModel.colum_avg_enabled)
        self.bgcolor_global.setEnabled(not self.is_series
                                       and self.dataModel.bgcolor_enabled)
        self.bgcolor_global.stateChanged.connect(self.dataModel.colum_avg)
        btn_layout.addWidget(self.bgcolor_global)

        btn_layout.addStretch()

        self.btn_save_and_close = QPushButton(_('Save and Close'))
        self.btn_save_and_close.setDisabled(True)
        self.btn_save_and_close.clicked.connect(self.accept)
        btn_layout.addWidget(self.btn_save_and_close)

        self.btn_close = QPushButton(_('Close'))
        self.btn_close.setAutoDefault(True)
        self.btn_close.setDefault(True)
        self.btn_close.clicked.connect(self.reject)
        btn_layout.addWidget(self.btn_close)

        self.layout.addLayout(btn_layout, 2, 0)

        return True

    @Slot(QModelIndex, QModelIndex)
    def save_and_close_enable(self, top_left, bottom_right):
        """Handle the data change event to enable the save and close button."""
        self.btn_save_and_close.setEnabled(True)
        self.btn_save_and_close.setAutoDefault(True)
        self.btn_save_and_close.setDefault(True)

    def change_bgcolor_enable(self, state):
        """
        This is implementet so column min/max is only active when bgcolor is
        """
        self.dataModel.bgcolor(state)
        self.bgcolor_global.setEnabled(not self.is_series and state > 0)

    def change_format(self):
        """
        Ask user for display format for floats and use it.

        This function also checks whether the format is valid and emits
        `sig_option_changed`.
        """
        format, valid = QInputDialog.getText(self, _('Format'),
                                             _("Float formatting"),
                                             QLineEdit.Normal,
                                             self.dataModel.get_format())
        if valid:
            format = str(format)
            try:
                format % 1.1
            except:
                msg = _("Format ({}) is incorrect").format(format)
                QMessageBox.critical(self, _("Error"), msg)
                return
            if not format.startswith('%'):
                msg = _("Format ({}) should start with '%'").format(format)
                QMessageBox.critical(self, _("Error"), msg)
                return
            self.dataModel.set_format(format)
            self.sig_option_changed.emit('dataframe_format', format)

    def get_value(self):
        """Return modified Dataframe -- this is *not* a copy"""
        # It is import to avoid accessing Qt C++ object as it has probably
        # already been destroyed, due to the Qt.WA_DeleteOnClose attribute
        df = self.dataModel.get_data()
        if self.is_series:
            return df.iloc[:, 0]
        else:
            return df

    def resize_to_contents(self):
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        self.dataTable.resizeColumnsToContents()
        self.dataModel.fetch_more(columns=True)
        self.dataTable.resizeColumnsToContents()
        QApplication.restoreOverrideCursor()
Пример #25
0
class DialogFindElements(QDialog):
    def __init__(self, parent=None):

        super().__init__(parent)

        self._dialog_data = {}

        self.setWindowTitle("Find Elements in Sample")

        # Check this flag after the dialog is exited with 'True' value
        #   If this flag is True, then run element search, if False,
        #   then simply save the changed parameter values.
        self.find_elements_requested = False

        self.validator = QDoubleValidator()

        self.le_e_calib_a0 = LineEditExtended()
        self.le_e_calib_a0.setValidator(self.validator)
        self.pb_e_calib_a0_default = QPushButton("Default")
        self.pb_e_calib_a0_default.setAutoDefault(False)

        self.le_e_calib_a1 = LineEditExtended()
        self.le_e_calib_a1.setValidator(self.validator)
        self.pb_e_calib_a1_default = QPushButton("Default")
        self.pb_e_calib_a1_default.setAutoDefault(False)

        self.le_e_calib_a2 = LineEditExtended()
        self.le_e_calib_a2.setValidator(self.validator)
        self.pb_e_calib_a2_default = QPushButton("Default")
        self.pb_e_calib_a2_default.setAutoDefault(False)

        self.group_energy_axis_calib = QGroupBox(
            "Polynomial Approximation of Energy Axis")
        set_tooltip(
            self.group_energy_axis_calib,
            "Parameters of polynomial approximation of <b>energy axis</b>. "
            "The values of the bins for photon energies are approximated "
            "using 2nd degree polynomial <b>E(n) = a0 + a1 * n + a2 * n^2</b>, "
            "where <b>n</b> is bin number (typically in the range 0..4096).",
        )
        grid = QGridLayout()
        grid.addWidget(QLabel("Bias (a0):"), 0, 0)
        grid.addWidget(self.le_e_calib_a0, 0, 1)
        grid.addWidget(self.pb_e_calib_a0_default, 0, 2)
        grid.addWidget(QLabel("Linear (a1):"), 1, 0)
        grid.addWidget(self.le_e_calib_a1, 1, 1)
        grid.addWidget(self.pb_e_calib_a1_default, 1, 2)
        grid.addWidget(QLabel("Quadratic (a2):"), 2, 0)
        grid.addWidget(self.le_e_calib_a2, 2, 1)
        grid.addWidget(self.pb_e_calib_a2_default, 2, 2)
        self.group_energy_axis_calib.setLayout(grid)

        self.le_fwhm_b1 = LineEditExtended()
        self.le_fwhm_b1.setValidator(self.validator)
        self.pb_fwhm_b1_default = QPushButton("Default")
        self.pb_fwhm_b1_default.setAutoDefault(False)

        self.le_fwhm_b2 = LineEditExtended()
        self.le_fwhm_b2.setValidator(self.validator)
        self.pb_fwhm_b2_default = QPushButton("Default")
        self.pb_fwhm_b2_default.setAutoDefault(False)

        self.group_fwhm = QGroupBox("Peak FWHM Settings")
        set_tooltip(
            self.group_fwhm,
            "Parameters used to estimate <b>FWHM</b> of peaks based on energy: "
            "<b>b1</b> - 'offset', <b>b2</b> - 'fanoprime'",
        )
        grid = QGridLayout()
        grid.addWidget(QLabel("Coefficient b1:"), 0, 0)
        grid.addWidget(self.le_fwhm_b1, 0, 1)
        grid.addWidget(self.pb_fwhm_b1_default, 0, 2)
        grid.addWidget(QLabel("Coefficient b2:"), 1, 0)
        grid.addWidget(self.le_fwhm_b2, 1, 1)
        grid.addWidget(self.pb_fwhm_b2_default, 1, 2)
        self.group_fwhm.setLayout(grid)

        self.le_incident_energy = LineEditExtended()
        self.le_incident_energy.setValidator(self.validator)
        set_tooltip(self.le_incident_energy, "<b>Incident energy</b> in keV.")
        self.pb_incident_energy_default = QPushButton("Default")
        self.pb_incident_energy_default.setAutoDefault(False)
        self.le_range_low = LineEditExtended()
        self.le_range_low.setValidator(self.validator)
        set_tooltip(self.le_range_low,
                    "<b>Lower boundary</b> of the selected range in keV.")
        self.pb_range_low_default = QPushButton("Default")
        self.pb_range_low_default.setAutoDefault(False)
        self.le_range_high = LineEditExtended()
        self.le_range_high.setValidator(self.validator)
        set_tooltip(self.le_range_high,
                    "<b>Upper boundary</b> of the selected range in keV.")
        self.pb_range_high_default = QPushButton("Default")
        self.pb_range_high_default.setAutoDefault(False)
        self.group_energy_range = QGroupBox(
            "Incident Energy and Selected Range")
        grid = QGridLayout()
        grid.addWidget(QLabel("Incident energy, keV"), 0, 0)
        grid.addWidget(self.le_incident_energy, 0, 1)
        grid.addWidget(self.pb_incident_energy_default, 0, 2)
        grid.addWidget(QLabel("Range (low), keV"), 1, 0)
        grid.addWidget(self.le_range_low, 1, 1)
        grid.addWidget(self.pb_range_low_default, 1, 2)
        grid.addWidget(QLabel("Range (high), keV"), 2, 0)
        grid.addWidget(self.le_range_high, 2, 1)
        grid.addWidget(self.pb_range_high_default, 2, 2)
        self.group_energy_range.setLayout(grid)

        self.pb_find_elements = QPushButton("Find &Elements")
        self.pb_find_elements.clicked.connect(self.pb_find_elements_clicked)
        self.pb_apply_settings = QPushButton("&Apply Settings")

        # 'Close' button box
        button_box = QDialogButtonBox(QDialogButtonBox.Cancel)
        button_box.addButton(self.pb_find_elements, QDialogButtonBox.YesRole)
        button_box.addButton(self.pb_apply_settings,
                             QDialogButtonBox.AcceptRole)
        button_box.button(QDialogButtonBox.Cancel).setDefault(True)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)

        self.pb_cancel = button_box.button(QDialogButtonBox.Cancel)
        self.pb_cancel.setAutoDefault(False)
        self.pb_apply_settings.setAutoDefault(True)

        vbox = QVBoxLayout()
        vbox.addWidget(self.group_energy_axis_calib)
        vbox.addWidget(self.group_fwhm)
        vbox.addWidget(self.group_energy_range)
        vbox.addWidget(button_box)

        self.setLayout(vbox)

        self.le_e_calib_a0.editingFinished.connect(
            self.le_e_calib_a0_editing_finished)
        self.le_e_calib_a1.editingFinished.connect(
            self.le_e_calib_a1_editing_finished)
        self.le_e_calib_a2.editingFinished.connect(
            self.le_e_calib_a2_editing_finished)
        self.le_fwhm_b1.editingFinished.connect(
            self.le_fwhm_b1_editing_finished)
        self.le_fwhm_b2.editingFinished.connect(
            self.le_fwhm_b2_editing_finished)
        self.le_incident_energy.editingFinished.connect(
            self.le_incident_energy_editing_finished)
        self.le_range_low.editingFinished.connect(
            self.le_range_low_editing_finished)
        self.le_range_high.editingFinished.connect(
            self.le_range_high_editing_finished)

        self.le_e_calib_a0.focusOut.connect(self.le_e_calib_a0_focus_out)
        self.le_e_calib_a1.focusOut.connect(self.le_e_calib_a1_focus_out)
        self.le_e_calib_a2.focusOut.connect(self.le_e_calib_a2_focus_out)
        self.le_fwhm_b1.focusOut.connect(self.le_fwhm_b1_focus_out)
        self.le_fwhm_b2.focusOut.connect(self.le_fwhm_b2_focus_out)
        self.le_incident_energy.focusOut.connect(
            self.le_incident_energy_focus_out)
        self.le_range_low.focusOut.connect(self.le_range_low_focus_out)
        self.le_range_high.focusOut.connect(self.le_range_high_focus_out)

        self.le_e_calib_a0.textChanged.connect(self.le_e_calib_a0_text_changed)
        self.le_e_calib_a1.textChanged.connect(self.le_e_calib_a1_text_changed)
        self.le_e_calib_a2.textChanged.connect(self.le_e_calib_a2_text_changed)
        self.le_fwhm_b1.textChanged.connect(self.le_fwhm_b1_text_changed)
        self.le_fwhm_b2.textChanged.connect(self.le_fwhm_b2_text_changed)
        self.le_incident_energy.textChanged.connect(
            self.le_incident_energy_text_changed)
        self.le_range_low.textChanged.connect(self.le_range_low_text_changed)
        self.le_range_high.textChanged.connect(self.le_range_high_text_changed)

        self.pb_e_calib_a0_default.clicked.connect(
            self.pb_e_calib_a0_default_clicked)
        self.pb_e_calib_a1_default.clicked.connect(
            self.pb_e_calib_a1_default_clicked)
        self.pb_e_calib_a2_default.clicked.connect(
            self.pb_e_calib_a2_default_clicked)
        self.pb_fwhm_b1_default.clicked.connect(
            self.pb_fwhm_b1_default_clicked)
        self.pb_fwhm_b2_default.clicked.connect(
            self.pb_fwhm_b2_default_clicked)
        self.pb_incident_energy_default.clicked.connect(
            self.pb_incident_energy_default_clicked)
        self.pb_range_low_default.clicked.connect(
            self.pb_range_low_default_clicked)
        self.pb_range_high_default.clicked.connect(
            self.pb_range_high_default_clicked)

    def _format_float(self, value):
        return f"{value:.10g}"

    def set_dialog_data(self, dialog_data):
        self._dialog_data = copy.deepcopy(dialog_data)
        self.le_e_calib_a0.setText(
            self._format_float(self._dialog_data["e_offset"]["value"]))
        self.le_e_calib_a1.setText(
            self._format_float(self._dialog_data["e_linear"]["value"]))
        self.le_e_calib_a2.setText(
            self._format_float(self._dialog_data["e_quadratic"]["value"]))
        self.le_fwhm_b1.setText(
            self._format_float(self._dialog_data["fwhm_offset"]["value"]))
        self.le_fwhm_b2.setText(
            self._format_float(self._dialog_data["fwhm_fanoprime"]["value"]))
        self.le_incident_energy.setText(
            self._format_float(
                self._dialog_data["coherent_sct_energy"]["value"]))
        self.le_range_low.setText(
            self._format_float(self._dialog_data["energy_bound_low"]["value"]))
        self.le_range_high.setText(
            self._format_float(
                self._dialog_data["energy_bound_high"]["value"]))

    def get_dialog_data(self):
        return self._dialog_data

    def pb_find_elements_clicked(self):
        self.find_elements_requested = True

    def _read_le_value(self, line_edit, value_ref):
        value_ref["value"] = float(line_edit.text())

    def le_e_calib_a0_editing_finished(self):
        self._read_le_value(self.le_e_calib_a0, self._dialog_data["e_offset"])

    def le_e_calib_a1_editing_finished(self):
        self._read_le_value(self.le_e_calib_a1, self._dialog_data["e_linear"])

    def le_e_calib_a2_editing_finished(self):
        self._read_le_value(self.le_e_calib_a2,
                            self._dialog_data["e_quadratic"])

    def le_fwhm_b1_editing_finished(self):
        self._read_le_value(self.le_fwhm_b1, self._dialog_data["fwhm_offset"])

    def le_fwhm_b2_editing_finished(self):
        self._read_le_value(self.le_fwhm_b2,
                            self._dialog_data["fwhm_fanoprime"])

    def le_incident_energy_editing_finished(self):
        self._read_le_value(self.le_incident_energy,
                            self._dialog_data["coherent_sct_energy"])
        self._read_le_value(self.le_range_high,
                            self._dialog_data["energy_bound_high"])

    def le_range_low_editing_finished(self):
        self._read_le_value(self.le_range_low,
                            self._dialog_data["energy_bound_low"])

    def le_range_high_editing_finished(self):
        self._read_le_value(self.le_range_high,
                            self._dialog_data["energy_bound_high"])

    def _validate_as_float(self, text):
        return self.validator.validate(text,
                                       0)[0] == QDoubleValidator.Acceptable

    def _validate_text(self, line_edit, text):
        line_edit.setValid(self._validate_as_float(text))
        self._update_exit_buttons_states()

    def _update_exit_buttons_states(self):
        if (self.le_e_calib_a0.isValid() and self.le_e_calib_a1.isValid()
                and self.le_e_calib_a2.isValid() and self.le_fwhm_b1.isValid()
                and self.le_fwhm_b2.isValid()
                and self.le_incident_energy.isValid()
                and self.le_range_low.isValid()
                and self.le_range_high.isValid()):
            all_valid = True
        else:
            all_valid = False

        self.pb_find_elements.setEnabled(all_valid)
        self.pb_apply_settings.setEnabled(all_valid)

    def _range_high_update(self, text, margin=0.8):
        """Update the range 'high' limit based on incident energy"""
        v = float(text) + margin
        self.le_range_high.setText(self._format_float(v))

    def le_e_calib_a0_text_changed(self, text):
        self._validate_text(self.le_e_calib_a0, text)

    def le_e_calib_a1_text_changed(self, text):
        self._validate_text(self.le_e_calib_a1, text)

    def le_e_calib_a2_text_changed(self, text):
        self._validate_text(self.le_e_calib_a2, text)

    def le_fwhm_b1_text_changed(self, text):
        self._validate_text(self.le_fwhm_b1, text)

    def le_fwhm_b2_text_changed(self, text):
        self._validate_text(self.le_fwhm_b2, text)

    def le_incident_energy_text_changed(self, text):
        if self._validate_as_float(text) and float(text) > 0:
            self.le_incident_energy.setValid(True)
            self._range_high_update(text)
        else:
            self.le_incident_energy.setValid(False)
        self._update_exit_buttons_states()

    def le_range_low_text_changed(self, text):
        text_valid = False
        if self._validate_as_float(text):
            v = float(text)
            if 0 <= v < self._dialog_data["energy_bound_high"]["value"]:
                text_valid = True
        self.le_range_low.setValid(text_valid)
        self.le_range_high.setValid(text_valid)
        self._update_exit_buttons_states()

    def le_range_high_text_changed(self, text):
        text_valid = False
        if self._validate_as_float(text):
            v = float(text)
            if v > self._dialog_data["energy_bound_low"]["value"]:
                text_valid = True
        self.le_range_high.setValid(text_valid)
        self.le_range_low.setValid(text_valid)
        self._update_exit_buttons_states()

    def _recover_last_valid_le_text(self, line_edit, last_value):
        if not self._validate_as_float(line_edit.text()):
            line_edit.setText(self._format_float(last_value["value"]))
            return False
        else:
            return True

    def le_e_calib_a0_focus_out(self):
        self._recover_last_valid_le_text(self.le_e_calib_a0,
                                         self._dialog_data["e_offset"])

    def le_e_calib_a1_focus_out(self):
        self._recover_last_valid_le_text(self.le_e_calib_a1,
                                         self._dialog_data["e_linear"])

    def le_e_calib_a2_focus_out(self):
        self._recover_last_valid_le_text(self.le_e_calib_a2,
                                         self._dialog_data["e_quadratic"])

    def le_fwhm_b1_focus_out(self):
        self._recover_last_valid_le_text(self.le_fwhm_b1,
                                         self._dialog_data["fwhm_offset"])

    def le_fwhm_b2_focus_out(self):
        self._recover_last_valid_le_text(self.le_fwhm_b2,
                                         self._dialog_data["fwhm_fanoprime"])

    def le_incident_energy_focus_out(self):
        if not self._recover_last_valid_le_text(
                self.le_incident_energy,
                self._dialog_data["coherent_sct_energy"]):
            self.le_range_high.setText(
                self._format_float(
                    self._dialog_data["energy_bound_high"]["value"]))

    def le_range_low_focus_out(self):
        self._recover_last_valid_le_text(self.le_range_low,
                                         self._dialog_data["energy_bound_low"])

    def le_range_high_focus_out(self):
        self._recover_last_valid_le_text(
            self.le_range_high, self._dialog_data["energy_bound_high"])

    def _reset_le_to_default(self, line_edit, data_ref):
        data_ref["value"] = data_ref["default"]
        line_edit.setText(self._format_float(data_ref["value"]))

    def pb_e_calib_a0_default_clicked(self):
        self._reset_le_to_default(self.le_e_calib_a0,
                                  self._dialog_data["e_offset"])

    def pb_e_calib_a1_default_clicked(self):
        self._reset_le_to_default(self.le_e_calib_a1,
                                  self._dialog_data["e_linear"])

    def pb_e_calib_a2_default_clicked(self):
        self._reset_le_to_default(self.le_e_calib_a2,
                                  self._dialog_data["e_quadratic"])

    def pb_fwhm_b1_default_clicked(self):
        self._reset_le_to_default(self.le_fwhm_b1,
                                  self._dialog_data["fwhm_offset"])

    def pb_fwhm_b2_default_clicked(self):
        self._reset_le_to_default(self.le_fwhm_b2,
                                  self._dialog_data["fwhm_fanoprime"])

    def pb_incident_energy_default_clicked(self):
        self._reset_le_to_default(self.le_incident_energy,
                                  self._dialog_data["coherent_sct_energy"])

    def pb_range_low_default_clicked(self):
        self._reset_le_to_default(self.le_range_low,
                                  self._dialog_data["energy_bound_low"])

    def pb_range_high_default_clicked(self):
        self._reset_le_to_default(self.le_range_high,
                                  self._dialog_data["energy_bound_high"])
Пример #26
0
class ObjectExplorer(BaseDialog, SpyderConfigurationAccessor):
    """Object explorer main widget window."""
    CONF_SECTION = 'variable_explorer'

    def __init__(self,
                 obj,
                 name='',
                 expanded=False,
                 resize_to_contents=True,
                 parent=None,
                 attribute_columns=DEFAULT_ATTR_COLS,
                 attribute_details=DEFAULT_ATTR_DETAILS,
                 readonly=None,
                 reset=False):
        """
        Constructor

        :param name: name of the object as it will appear in the root node
        :param expanded: show the first visible root element expanded
        :param resize_to_contents: resize columns to contents ignoring width
            of the attributes
        :param obj: any Python object or variable
        :param attribute_columns: list of AttributeColumn objects that
            define which columns are present in the table and their defaults
        :param attribute_details: list of AttributeDetails objects that define
            which attributes can be selected in the details pane.
        :param reset: If true the persistent settings, such as column widths,
            are reset.
        """
        QDialog.__init__(self, parent=parent)
        self.setAttribute(Qt.WA_DeleteOnClose)

        # Options
        show_callable_attributes = self.get_conf('show_callable_attributes')
        show_special_attributes = self.get_conf('show_special_attributes')

        # Model
        self._attr_cols = attribute_columns
        self._attr_details = attribute_details
        self.readonly = readonly

        self.btn_save_and_close = None
        self.btn_close = None

        self._tree_model = TreeModel(obj,
                                     obj_name=name,
                                     attr_cols=self._attr_cols)

        self._proxy_tree_model = TreeProxyModel(
            show_callable_attributes=show_callable_attributes,
            show_special_attributes=show_special_attributes)

        self._proxy_tree_model.setSourceModel(self._tree_model)
        # self._proxy_tree_model.setSortRole(RegistryTableModel.SORT_ROLE)
        self._proxy_tree_model.setDynamicSortFilter(True)
        # self._proxy_tree_model.setSortCaseSensitivity(Qt.CaseInsensitive)

        # Tree widget
        self.obj_tree = ToggleColumnTreeView()
        self.obj_tree.setAlternatingRowColors(True)
        self.obj_tree.setModel(self._proxy_tree_model)
        self.obj_tree.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.obj_tree.setUniformRowHeights(True)
        self.obj_tree.add_header_context_menu()

        # Views
        self._setup_actions()
        self._setup_menu(show_callable_attributes=show_callable_attributes,
                         show_special_attributes=show_special_attributes)
        self._setup_views()
        if name:
            name = "{} -".format(name)
        self.setWindowTitle("{} {}".format(name, EDITOR_NAME))
        self.setWindowFlags(Qt.Window)

        self._resize_to_contents = resize_to_contents
        self._readViewSettings(reset=reset)

        # Update views with model
        self.toggle_show_special_attribute_action.setChecked(
            show_special_attributes)
        self.toggle_show_callable_action.setChecked(show_callable_attributes)

        # Select first row so that a hidden root node will not be selected.
        first_row_index = self._proxy_tree_model.firstItemIndex()
        self.obj_tree.setCurrentIndex(first_row_index)
        if self._tree_model.inspectedNodeIsVisible or expanded:
            self.obj_tree.expand(first_row_index)

    def get_value(self):
        """Get editor current object state."""
        return self._tree_model.inspectedItem.obj

    def _make_show_column_function(self, column_idx):
        """Creates a function that shows or hides a column."""
        show_column = lambda checked: self.obj_tree.setColumnHidden(
            column_idx, not checked)
        return show_column

    def _setup_actions(self):
        """Creates the main window actions."""
        # Show/hide callable objects
        self.toggle_show_callable_action = QAction(
            _("Show callable attributes"),
            self,
            checkable=True,
            shortcut=QKeySequence("Alt+C"),
            statusTip=_("Shows/hides attributes that are callable "
                        "(functions, methods, etc)"))
        self.toggle_show_callable_action.toggled.connect(
            self._proxy_tree_model.setShowCallables)
        self.toggle_show_callable_action.toggled.connect(
            self.obj_tree.resize_columns_to_contents)

        # Show/hide special attributes
        self.toggle_show_special_attribute_action = QAction(
            _("Show __special__ attributes"),
            self,
            checkable=True,
            shortcut=QKeySequence("Alt+S"),
            statusTip=_("Shows or hides __special__ attributes"))
        self.toggle_show_special_attribute_action.toggled.connect(
            self._proxy_tree_model.setShowSpecialAttributes)
        self.toggle_show_special_attribute_action.toggled.connect(
            self.obj_tree.resize_columns_to_contents)

    def _setup_menu(self,
                    show_callable_attributes=False,
                    show_special_attributes=False):
        """Sets up the main menu."""
        self.tools_layout = QHBoxLayout()

        callable_attributes = create_toolbutton(
            self,
            text=_("Show callable attributes"),
            icon=ima.icon("class"),
            toggled=self._toggle_show_callable_attributes_action)
        callable_attributes.setCheckable(True)
        callable_attributes.setChecked(show_callable_attributes)
        self.tools_layout.addWidget(callable_attributes)

        special_attributes = create_toolbutton(
            self,
            text=_("Show __special__ attributes"),
            icon=ima.icon("private2"),
            toggled=self._toggle_show_special_attributes_action)
        special_attributes.setCheckable(True)
        special_attributes.setChecked(show_special_attributes)
        self.tools_layout.addWidget(special_attributes)

        self.tools_layout.addStretch()

        self.options_button = create_toolbutton(self,
                                                text=_('Options'),
                                                icon=ima.icon('tooloptions'))
        self.options_button.setPopupMode(QToolButton.InstantPopup)

        self.show_cols_submenu = QMenu(self)
        self.options_button.setMenu(self.show_cols_submenu)
        # Don't show menu arrow and remove padding
        if is_dark_interface():
            self.options_button.setStyleSheet(
                ("QToolButton::menu-indicator{image: none;}\n"
                 "QToolButton{padding: 3px;}"))
        else:
            self.options_button.setStyleSheet(
                "QToolButton::menu-indicator{image: none;}")
        self.tools_layout.addWidget(self.options_button)

    @Slot()
    def _toggle_show_callable_attributes_action(self):
        """Toggle show callable atributes action."""
        action_checked = not self.toggle_show_callable_action.isChecked()
        self.toggle_show_callable_action.setChecked(action_checked)
        self.set_conf('show_callable_attributes', action_checked)

    @Slot()
    def _toggle_show_special_attributes_action(self):
        """Toggle show special attributes action."""
        action_checked = (
            not self.toggle_show_special_attribute_action.isChecked())
        self.toggle_show_special_attribute_action.setChecked(action_checked)
        self.set_conf('show_special_attributes', action_checked)

    def _setup_views(self):
        """Creates the UI widgets."""
        self.central_splitter = QSplitter(self, orientation=Qt.Vertical)
        layout = create_plugin_layout(self.tools_layout, self.central_splitter)
        self.setLayout(layout)

        # Stretch last column?
        # It doesn't play nice when columns are hidden and then shown again.
        obj_tree_header = self.obj_tree.header()
        obj_tree_header.setSectionsMovable(True)
        obj_tree_header.setStretchLastSection(False)
        add_actions(self.show_cols_submenu,
                    self.obj_tree.toggle_column_actions_group.actions())

        self.central_splitter.addWidget(self.obj_tree)

        # Bottom pane
        bottom_pane_widget = QWidget()
        bottom_layout = QHBoxLayout()
        bottom_layout.setSpacing(0)
        bottom_layout.setContentsMargins(5, 5, 5, 5)  # left top right bottom
        bottom_pane_widget.setLayout(bottom_layout)
        self.central_splitter.addWidget(bottom_pane_widget)

        group_box = QGroupBox(_("Details"))
        bottom_layout.addWidget(group_box)

        v_group_layout = QVBoxLayout()
        h_group_layout = QHBoxLayout()
        h_group_layout.setContentsMargins(2, 2, 2, 2)  # left top right bottom
        group_box.setLayout(v_group_layout)
        v_group_layout.addLayout(h_group_layout)

        # Radio buttons
        radio_widget = QWidget()
        radio_layout = QVBoxLayout()
        radio_layout.setContentsMargins(0, 0, 0, 0)  # left top right bottom
        radio_widget.setLayout(radio_layout)

        self.button_group = QButtonGroup(self)
        for button_id, attr_detail in enumerate(self._attr_details):
            radio_button = QRadioButton(attr_detail.name)
            radio_layout.addWidget(radio_button)
            self.button_group.addButton(radio_button, button_id)

        self.button_group.buttonClicked[int].connect(
            self._change_details_field)
        self.button_group.button(0).setChecked(True)

        radio_layout.addStretch(1)
        h_group_layout.addWidget(radio_widget)

        # Editor widget
        self.editor = SimpleCodeEditor(self)
        self.editor.setReadOnly(True)
        h_group_layout.addWidget(self.editor)

        # Save and close buttons
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()

        if not self.readonly:
            self.btn_save_and_close = QPushButton(_('Save and Close'))
            self.btn_save_and_close.setDisabled(True)
            self.btn_save_and_close.clicked.connect(self.accept)
            btn_layout.addWidget(self.btn_save_and_close)

        self.btn_close = QPushButton(_('Close'))
        self.btn_close.setAutoDefault(True)
        self.btn_close.setDefault(True)
        self.btn_close.clicked.connect(self.reject)
        btn_layout.addWidget(self.btn_close)
        v_group_layout.addLayout(btn_layout)

        # Splitter parameters
        self.central_splitter.setCollapsible(0, False)
        self.central_splitter.setCollapsible(1, True)
        self.central_splitter.setSizes([500, 320])

        # Connect signals
        # Keep a temporary reference of the selection_model to prevent
        # segfault in PySide.
        # See http://permalink.gmane.org/gmane.comp.lib.qt.pyside.devel/222
        selection_model = self.obj_tree.selectionModel()
        selection_model.currentChanged.connect(self._update_details)

        # Check if the values of the model have been changed
        self._proxy_tree_model.sig_setting_data.connect(
            self.save_and_close_enable)

        self._proxy_tree_model.sig_update_details.connect(
            self._update_details_for_item)

    # End of setup_methods
    def _readViewSettings(self, reset=False):
        """
        Reads the persistent program settings.

        :param reset: If True, the program resets to its default settings.
        """
        pos = QPoint(20, 20)
        window_size = QSize(825, 650)
        details_button_idx = 0

        header = self.obj_tree.header()
        header_restored = False

        if reset:
            logger.debug("Resetting persistent view settings")
        else:
            pos = pos
            window_size = window_size
            details_button_idx = details_button_idx
            #            splitter_state = settings.value("central_splitter/state")
            splitter_state = None
            if splitter_state:
                self.central_splitter.restoreState(splitter_state)
#            header_restored = self.obj_tree.read_view_settings(
#                'table/header_state',
#                settings, reset)
            header_restored = False

        if not header_restored:
            column_sizes = [col.width for col in self._attr_cols]
            column_visible = [col.col_visible for col in self._attr_cols]

            for idx, size in enumerate(column_sizes):
                if not self._resize_to_contents and size > 0:  # Just in case
                    header.resizeSection(idx, size)
                else:
                    header.resizeSections(QHeaderView.ResizeToContents)
                    break

            for idx, visible in enumerate(column_visible):
                elem = self.obj_tree.toggle_column_actions_group.actions()[idx]
                elem.setChecked(visible)

        self.resize(window_size)

        button = self.button_group.button(details_button_idx)
        if button is not None:
            button.setChecked(True)

    @Slot()
    def save_and_close_enable(self):
        """Handle the data change event to enable the save and close button."""
        if self.btn_save_and_close:
            self.btn_save_and_close.setEnabled(True)
            self.btn_save_and_close.setAutoDefault(True)
            self.btn_save_and_close.setDefault(True)

    @Slot(QModelIndex, QModelIndex)
    def _update_details(self, current_index, _previous_index):
        """Shows the object details in the editor given an index."""
        tree_item = self._proxy_tree_model.treeItem(current_index)
        self._update_details_for_item(tree_item)

    def _change_details_field(self, _button_id=None):
        """Changes the field that is displayed in the details pane."""
        # logger.debug("_change_details_field: {}".format(_button_id))
        current_index = self.obj_tree.selectionModel().currentIndex()
        tree_item = self._proxy_tree_model.treeItem(current_index)
        self._update_details_for_item(tree_item)

    @Slot(TreeItem)
    def _update_details_for_item(self, tree_item):
        """Shows the object details in the editor given an tree_item."""
        try:
            # obj = tree_item.obj
            button_id = self.button_group.checkedId()
            assert button_id >= 0, ("No radio button selected. "
                                    "Please report this bug.")
            attr_details = self._attr_details[button_id]
            data = attr_details.data_fn(tree_item)
            self.editor.setPlainText(data)
            self.editor.setWordWrapMode(attr_details.line_wrap)
            self.editor.setup_editor(
                font=get_font(font_size_delta=DEFAULT_SMALL_DELTA),
                show_blanks=False,
                color_scheme=CONF.get('appearance', 'selected'),
                scroll_past_end=False,
            )
            self.editor.set_text(data)

            if attr_details.name == 'Source code':
                self.editor.set_language('Python')
            else:
                self.editor.set_language('Rst')

        except Exception as ex:
            self.editor.setStyleSheet("color: red;")
            stack_trace = traceback.format_exc()
            self.editor.setPlainText("{}\n\n{}".format(ex, stack_trace))
            self.editor.setWordWrapMode(
                QTextOption.WrapAtWordBoundaryOrAnywhere)

    @classmethod
    def create_explorer(cls, *args, **kwargs):
        """
        Creates and shows and ObjectExplorer window.

        The *args and **kwargs will be passed to the ObjectExplorer constructor

        A (class attribute) reference to the browser window is kept to prevent
        it from being garbage-collected.
        """
        object_explorer = cls(*args, **kwargs)
        object_explorer.exec_()
        return object_explorer
Пример #27
0
    def _setup_ll_list_wid(self):
        ll_list_layout = QGridLayout(self.ll_list_wid)
        ll_list_layout.setHorizontalSpacing(20)
        ll_list_layout.setVerticalSpacing(20)

        but = QPushButton('Open LL Triggers', self)
        but.setAutoDefault(False)
        but.setDefault(False)
        obj_names = HLTimeSearch.get_ll_trigger_names(self.device.device_name)
        icon = qta.icon('mdi.timer',
                        color=get_appropriate_color(self.device.sec))
        Window = create_window_from_widget(LLTriggers,
                                           title=self.device.device_name +
                                           ': LL Triggers',
                                           icon=icon)
        connect_window(but,
                       Window,
                       self,
                       prefix=self.prefix,
                       hltrigger=self.device.device_name,
                       obj_names=obj_names)
        ll_list_layout.addWidget(but, 0, 0, 1, 2)

        init_channel = self.get_pvname('LowLvlLock-Sel')
        sp = PyDMStateButton(self, init_channel=init_channel)
        init_channel = self.get_pvname('LowLvlLock-Sts')
        rb = PyDMLed(self, init_channel=init_channel)
        gb = self._create_small_group('Lock Low Level', self.ll_list_wid,
                                      (sp, rb))
        ll_list_layout.addWidget(gb, 1, 0)

        init_channel = self.get_pvname('State-Sel')
        sp = PyDMStateButton(self, init_channel=init_channel)
        init_channel = self.get_pvname('State-Sts')
        rb = PyDMLed(self, init_channel=init_channel)
        gb = self._create_small_group('Enabled', self.ll_list_wid, (sp, rb))
        ll_list_layout.addWidget(gb, 1, 1)

        init_channel = self.get_pvname('Polarity-Sel')
        sp = SiriusEnumComboBox(self, init_channel=init_channel)
        init_channel = self.get_pvname('Polarity-Sts')
        rb = PyDMLabel(self, init_channel=init_channel)
        gb = self._create_small_group('Polarity', self.ll_list_wid, (sp, rb))
        ll_list_layout.addWidget(gb, 2, 0)

        init_channel = self.get_pvname('Src-Sel')
        sp = SiriusEnumComboBox(self, init_channel=init_channel)
        init_channel = self.get_pvname('Src-Sts')
        rb = PyDMLabel(self, init_channel=init_channel)
        gb = self._create_small_group('Source', self.ll_list_wid, (sp, rb))
        ll_list_layout.addWidget(gb, 2, 1)

        init_channel = self.get_pvname('NrPulses-SP')
        sp = SiriusSpinbox(self, init_channel=init_channel)
        sp.showStepExponent = False
        init_channel = self.get_pvname('NrPulses-RB')
        rb = PyDMLabel(self, init_channel=init_channel)
        gb = self._create_small_group('Nr Pulses', self.ll_list_wid, (sp, rb))
        ll_list_layout.addWidget(gb, 3, 0)

        init_channel = self.get_pvname('Duration-SP')
        sp = SiriusSpinbox(self, init_channel=init_channel)
        sp.showStepExponent = False
        init_channel = self.get_pvname('Duration-RB')
        rb = PyDMLabel(self, init_channel=init_channel)
        gb = self._create_small_group('Duration [us]', self.ll_list_wid,
                                      (sp, rb))
        ll_list_layout.addWidget(gb, 3, 1)

        init_channel = self.get_pvname('Delay-SP')
        sp = SiriusSpinbox(self, init_channel=init_channel)
        sp.showStepExponent = False
        init_channel = self.get_pvname('Delay-RB')
        rb = PyDMLabel(self, init_channel=init_channel)
        gbdel = self._create_small_group('[us]', self.ll_list_wid, (sp, rb))

        init_channel = self.get_pvname('DelayRaw-SP')
        sp = SiriusSpinbox(self, init_channel=init_channel)
        sp.showStepExponent = False
        init_channel = self.get_pvname('DelayRaw-RB')
        rb = PyDMLabel(self, init_channel=init_channel)
        gbdelr = self._create_small_group('Raw', self.ll_list_wid, (sp, rb))

        init_channel = self.get_pvname('TotalDelay-Mon')
        rb = PyDMLabel(self, init_channel=init_channel)
        gbtdel = self._create_small_group('[us]', self.ll_list_wid, (rb, ))

        init_channel = self.get_pvname('TotalDelayRaw-Mon')
        rb = PyDMLabel(self, init_channel=init_channel)
        gbtdelr = self._create_small_group('Raw', self.ll_list_wid, (rb, ))

        widd = QWidget(self.ll_list_wid)
        widd.setLayout(QHBoxLayout())
        widd.layout().addWidget(gbdel)
        widd.layout().addWidget(gbdelr)

        widt = QWidget(self.ll_list_wid)
        widt.setLayout(QHBoxLayout())
        widt.layout().addWidget(gbtdel)
        widt.layout().addWidget(gbtdelr)

        tabdel = QTabWidget(self)
        tabdel.setObjectName(self.device.sec + 'Tab')
        tabdel.addTab(widd, 'Delay')
        tabdel.addTab(widt, 'Total Delay')

        if HLTimeSearch.has_delay_type(self.device.device_name):
            init_channel = self.get_pvname('RFDelayType-Sel')
            sp = SiriusEnumComboBox(self, init_channel=init_channel)
            init_channel = self.get_pvname('RFDelayType-Sts')
            rb = PyDMLabel(self, init_channel=init_channel)
            gb = self._create_small_group('Delay Type', self.ll_list_wid,
                                          (sp, rb))
            ll_list_layout.addWidget(gb, 4, 0)
            ll_list_layout.addWidget(tabdel, 4, 1)
        else:
            ll_list_layout.addWidget(tabdel, 4, 0, 1, 2)

        gbdelta = self._create_deltadelay()
        ll_list_layout.addWidget(gbdelta, 0, 2, 5, 1)
Пример #28
0
class ArrayEditor(QDialog):
    """Array Editor Dialog"""
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)

        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)

        self.data = None
        self.arraywidget = None
        self.stack = None
        self.layout = None
        self.btn_save_and_close = None
        self.btn_close = None
        # Values for 3d array editor
        self.dim_indexes = [{}, {}, {}]
        self.last_dim = 0  # Adjust this for changing the startup dimension

    def setup_and_check(self,
                        data,
                        title='',
                        readonly=False,
                        xlabels=None,
                        ylabels=None):
        """
        Setup ArrayEditor:
        return False if data is not supported, True otherwise
        """
        self.data = data
        self.data.flags.writeable = True
        is_record_array = data.dtype.names is not None
        is_masked_array = isinstance(data, np.ma.MaskedArray)

        if data.ndim > 3:
            self.error(
                _("Arrays with more than 3 dimensions are not "
                  "supported"))
            return False
        if xlabels is not None and len(xlabels) != self.data.shape[1]:
            self.error(
                _("The 'xlabels' argument length do no match array "
                  "column number"))
            return False
        if ylabels is not None and len(ylabels) != self.data.shape[0]:
            self.error(
                _("The 'ylabels' argument length do no match array row "
                  "number"))
            return False
        if not is_record_array:
            dtn = data.dtype.name
            if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') \
               and not dtn.startswith('unicode'):
                arr = _("%s arrays") % data.dtype.name
                self.error(_("%s are currently not supported") % arr)
                return False

        self.layout = QGridLayout()
        self.setLayout(self.layout)
        self.setWindowIcon(ima.icon('arredit'))
        if title:
            title = to_text_string(title) + " - " + _("NumPy array")
        else:
            title = _("Array editor")
        if readonly:
            title += ' (' + _('read only') + ')'
        self.setWindowTitle(title)
        self.resize(600, 500)

        # Stack widget
        self.stack = QStackedWidget(self)
        if is_record_array:
            for name in data.dtype.names:
                self.stack.addWidget(
                    ArrayEditorWidget(self, data[name], readonly, xlabels,
                                      ylabels))
        elif is_masked_array:
            self.stack.addWidget(
                ArrayEditorWidget(self, data, readonly, xlabels, ylabels))
            self.stack.addWidget(
                ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels))
            self.stack.addWidget(
                ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels))
        elif data.ndim == 3:
            pass
        else:
            self.stack.addWidget(
                ArrayEditorWidget(self, data, readonly, xlabels, ylabels))
        self.arraywidget = self.stack.currentWidget()
        if self.arraywidget:
            self.arraywidget.model.dataChanged.connect(
                self.save_and_close_enable)
        self.stack.currentChanged.connect(self.current_widget_changed)
        self.layout.addWidget(self.stack, 1, 0)

        # Buttons configuration
        btn_layout = QHBoxLayout()
        if is_record_array or is_masked_array or data.ndim == 3:
            if is_record_array:
                btn_layout.addWidget(QLabel(_("Record array fields:")))
                names = []
                for name in data.dtype.names:
                    field = data.dtype.fields[name]
                    text = name
                    if len(field) >= 3:
                        title = field[2]
                        if not is_text_string(title):
                            title = repr(title)
                        text += ' - ' + title
                    names.append(text)
            else:
                names = [_('Masked data'), _('Data'), _('Mask')]
            if data.ndim == 3:
                # QSpinBox
                self.index_spin = QSpinBox(self, keyboardTracking=False)
                self.index_spin.valueChanged.connect(self.change_active_widget)
                # QComboBox
                names = [str(i) for i in range(3)]
                ra_combo = QComboBox(self)
                ra_combo.addItems(names)
                ra_combo.currentIndexChanged.connect(self.current_dim_changed)
                # Adding the widgets to layout
                label = QLabel(_("Axis:"))
                btn_layout.addWidget(label)
                btn_layout.addWidget(ra_combo)
                self.shape_label = QLabel()
                btn_layout.addWidget(self.shape_label)
                label = QLabel(_("Index:"))
                btn_layout.addWidget(label)
                btn_layout.addWidget(self.index_spin)
                self.slicing_label = QLabel()
                btn_layout.addWidget(self.slicing_label)
                # set the widget to display when launched
                self.current_dim_changed(self.last_dim)
            else:
                ra_combo = QComboBox(self)
                ra_combo.currentIndexChanged.connect(
                    self.stack.setCurrentIndex)
                ra_combo.addItems(names)
                btn_layout.addWidget(ra_combo)
            if is_masked_array:
                label = QLabel(
                    _("<u>Warning</u>: changes are applied separately"))
                label.setToolTip(_("For performance reasons, changes applied "\
                                   "to masked array won't be reflected in "\
                                   "array's data (and vice-versa)."))
                btn_layout.addWidget(label)

        btn_layout.addStretch()

        if not readonly:
            self.btn_save_and_close = QPushButton(_('Save and Close'))
            self.btn_save_and_close.setDisabled(True)
            self.btn_save_and_close.clicked.connect(self.accept)
            btn_layout.addWidget(self.btn_save_and_close)

        self.btn_close = QPushButton(_('Close'))
        self.btn_close.setAutoDefault(True)
        self.btn_close.setDefault(True)
        self.btn_close.clicked.connect(self.reject)
        btn_layout.addWidget(self.btn_close)
        self.layout.addLayout(btn_layout, 2, 0)

        self.setMinimumSize(400, 300)

        # Make the dialog act as a window
        self.setWindowFlags(Qt.Window)

        return True

    @Slot(QModelIndex, QModelIndex)
    def save_and_close_enable(self, left_top, bottom_right):
        """Handle the data change event to enable the save and close button."""
        if self.btn_save_and_close:
            self.btn_save_and_close.setEnabled(True)
            self.btn_save_and_close.setAutoDefault(True)
            self.btn_save_and_close.setDefault(True)

    def current_widget_changed(self, index):
        self.arraywidget = self.stack.widget(index)
        self.arraywidget.model.dataChanged.connect(self.save_and_close_enable)

    def change_active_widget(self, index):
        """
        This is implemented for handling negative values in index for
        3d arrays, to give the same behavior as slicing
        """
        string_index = [':'] * 3
        string_index[self.last_dim] = '<font color=red>%i</font>'
        self.slicing_label.setText(
            (r"Slicing: [" + ", ".join(string_index) + "]") % index)
        if index < 0:
            data_index = self.data.shape[self.last_dim] + index
        else:
            data_index = index
        slice_index = [slice(None)] * 3
        slice_index[self.last_dim] = data_index

        stack_index = self.dim_indexes[self.last_dim].get(data_index)
        if stack_index == None:
            stack_index = self.stack.count()
            try:
                self.stack.addWidget(
                    ArrayEditorWidget(self, self.data[slice_index]))
            except IndexError:  # Handle arrays of size 0 in one axis
                self.stack.addWidget(ArrayEditorWidget(self, self.data))
            self.dim_indexes[self.last_dim][data_index] = stack_index
            self.stack.update()
        self.stack.setCurrentIndex(stack_index)

    def current_dim_changed(self, index):
        """
        This change the active axis the array editor is plotting over
        in 3D
        """
        self.last_dim = index
        string_size = ['%i'] * 3
        string_size[index] = '<font color=red>%i</font>'
        self.shape_label.setText(
            ('Shape: (' + ', '.join(string_size) + ')    ') % self.data.shape)
        if self.index_spin.value() != 0:
            self.index_spin.setValue(0)
        else:
            # this is done since if the value is currently 0 it does not emit
            # currentIndexChanged(int)
            self.change_active_widget(0)
        self.index_spin.setRange(-self.data.shape[index],
                                 self.data.shape[index] - 1)

    @Slot()
    def accept(self):
        """Reimplement Qt method"""
        for index in range(self.stack.count()):
            self.stack.widget(index).accept_changes()
        QDialog.accept(self)

    def get_value(self):
        """Return modified array -- this is *not* a copy"""
        # It is import to avoid accessing Qt C++ object as it has probably
        # already been destroyed, due to the Qt.WA_DeleteOnClose attribute
        return self.data

    def error(self, message):
        """An error occured, closing the dialog box"""
        QMessageBox.critical(self, _("Array editor"), message)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.reject()

    @Slot()
    def reject(self):
        """Reimplement Qt method"""
        if self.arraywidget is not None:
            for index in range(self.stack.count()):
                self.stack.widget(index).reject_changes()
        QDialog.reject(self)
Пример #29
0
class PreferencesDialog(DialogBase):
    sig_urls_updated = Signal(str, str)
    sig_show_application_environments = Signal(bool)

    def __init__(self, *args, **kwargs):
        super(PreferencesDialog, self).__init__(*args, **kwargs)

        self.api = AnacondaAPI()
        self.widgets_changed = set()
        self.widgets = []

        # Widgets
        self.button_ok = QPushButton('Ok')
        self.button_cancel = ButtonCancel('Cancel')
        self.button_reset = QPushButton('Reset to defaults')
        self.row = 0

        # Widget setup
        self.setWindowTitle("Preferences")

        # Layouts
        self.grid_layout = QGridLayout()

        buttons_layout = QHBoxLayout()
        buttons_layout.addWidget(self.button_reset)
        buttons_layout.addStretch()
        buttons_layout.addWidget(self.button_ok)
        buttons_layout.addWidget(self.button_cancel)

        main_layout = QVBoxLayout()
        main_layout.addLayout(self.grid_layout)
        main_layout.addSpacing(40)
        main_layout.addLayout(buttons_layout)
        self.setLayout(main_layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.button_reset.clicked.connect(self.reset_to_defaults)
        self.button_reset.clicked.connect(
            lambda: self.button_ok.setEnabled(True))

        # Setup
        self.grid_layout.setSpacing(0)
        self.setup()
        self.button_ok.setDisabled(True)
        self.widgets[0].setFocus()
        self.button_ok.setDefault(True)
        self.button_ok.setAutoDefault(True)

    # --- Helpers
    # -------------------------------------------------------------------------
    def get_option(self, option):
        return CONF.get('main', option, None)

    def set_option(self, option, value, default=False):
        CONF.set('main', option, value)

    def set_option_default(self, option):
        default = CONF.get_default('main', option)
        self.set_option(option, default)

    def create_widget(self,
                      widget=None,
                      label=None,
                      option=None,
                      hint=None,
                      check=None):
        if hint:
            widget.setPlaceholderText(hint)

        config_value = self.get_option(option)
        widget.label = QLabel(label)
        widget.option = option
        widget.set_value(config_value)
        widget.label_information = QLabel()
        widget.label_information.setMinimumWidth(20)
        widget.label_information.setMaximumWidth(20)

        form_widget = QWidget()
        h_layout = QHBoxLayout()
        h_layout.addSpacing(4)
        h_layout.addWidget(widget.label_information, 0, Qt.AlignRight)
        h_layout.addWidget(widget, 0, Qt.AlignLeft)
        form_widget.setLayout(h_layout)

        if check:
            widget.check_value = lambda value: check(value)
        else:
            widget.check_value = lambda value: (True, '')

        self.widgets.append(widget)
        self.grid_layout.addWidget(widget.label, self.row, 0,
                                   Qt.AlignRight | Qt.AlignCenter)
        self.grid_layout.addWidget(form_widget, self.row, 1,
                                   Qt.AlignLeft | Qt.AlignCenter)
        self.row += 1

    def create_textbox(self, label, option, hint=None, regex=None, check=None):
        widget = QLineEdit()
        widget.setAttribute(Qt.WA_MacShowFocusRect, False)
        widget.setMinimumWidth(250)

        if regex:
            regex_validator = QRegExpValidator(QRegExp(regex))
            widget.setValidator(regex_validator)

        widget.get_value = lambda w=widget: w.text()
        widget.set_value = lambda value, w=widget: w.setText(value)
        widget.set_warning = lambda w=widget: w.setSelection(0, 1000)
        widget.textChanged.connect(
            lambda v=None, w=widget: self.options_changed(widget=w))

        self.create_widget(widget=widget,
                           option=option,
                           label=label,
                           hint=hint,
                           check=check)

    def create_checkbox(self, label, option, hint=None):
        widget = QCheckBox()
        widget.get_value = lambda w=widget: bool(w.checkState())
        widget.set_value = lambda value, w=widget: bool(
            w.setCheckState(Qt.Checked if value else Qt.Unchecked))
        widget.stateChanged.connect(
            lambda v=None, w=widget: self.options_changed(widget=w))
        self.create_widget(widget=widget,
                           option=option,
                           label=label,
                           hint=hint)

    def options_changed(self, value=None, widget=None):
        config_value = self.get_option(widget.option)

        if config_value != widget.get_value():
            self.widgets_changed.add(widget)
        else:
            if widget in self.widgets_changed:
                self.widgets_changed.remove(widget)

        self.button_ok.setDisabled(not bool(len(self.widgets_changed)))

    # --- API
    # -------------------------------------------------------------------------
    def setup(self):
        self.create_textbox('Anaconda API domain',
                            'anaconda_api_url',
                            check=self.is_valid_api)
        self.create_textbox('Conda domain',
                            'conda_url',
                            check=self.is_valid_url)
        self.create_checkbox('Provide analytics', 'provide_analytics')
        self.create_checkbox("Hide quit dialog", 'hide_quit_dialog')


#        self.create_checkbox('Show application<br>environments',
#                             'show_application_environments')

    def warn(self, widget, text=None):
        """
        """
        label = widget.label_information
        if text:
            pixmap = QPixmap(WARNING_ICON)
            label.setPixmap(
                pixmap.scaled(16, 16, Qt.KeepAspectRatio,
                              Qt.SmoothTransformation))
            label.setToolTip(str(text))
        else:
            label.setPixmap(QPixmap())
            label.setToolTip('')

    # --- Checkers
    # -------------------------------------------------------------------------
    def is_valid_url(self, url):
        """
        Chek if a given URL returns a 200 code.
        """
        output = self.api.download_is_valid_url(url, non_blocking=False)
        error = ''
        if not output:
            error = 'Invalid url'
        return output, error

    def is_valid_api(self, url):
        """
        Chek if a given URL is a valid anaconda api endpoint.
        """
        output = self.api.download_is_valid_api_url(url, non_blocking=False)
        error = ''
        if not output:
            error = 'Invalid Anaconda API url.'
        return output, error

    def reset_to_defaults(self):
        """
        """
        # ASK!
        for widget in self.widgets:
            option = widget.option
            self.set_option_default(option)
            value = self.get_option(option)
            widget.set_value(value)

    def accept(self):
        """
        Qt override.
        """
        sig_updated = False
        for widget in self.widgets_changed:
            option = widget.option
            value = widget.get_value()
            check, error = widget.check_value(value)

            if check:
                self.set_option(option, value)
                self.warn(widget)
            else:
                self.button_ok.setDisabled(True)
                widget.set_warning()
                self.warn(widget, error)
                return

            if widget.option == 'conda_url':
                sig_updated = True

            if widget.option == 'anaconda_api_url':
                sig_updated = True

            if widget.option == 'show_application_environments':
                self.sig_show_application_environments.emit(value)

        for widget in self.widgets:
            if widget.option == 'anaconda_api_url':
                anaconda_api_url = widget.get_value()
            elif widget.option == 'conda_url':
                conda_url = widget.get_value()

        if sig_updated:
            self.sig_urls_updated.emit(anaconda_api_url, conda_url)

        super(PreferencesDialog, self).accept()