示例#1
0
class CloneDialog(EnvironmentActionsDialog):
    """Clone environment dialog."""
    def __init__(self, parent=None, clone_from_name=None):
        """Clone environment dialog."""
        super(CloneDialog, self).__init__(parent=parent)

        # Widgets
        self.label_name = LabelBase("Name:")
        self.text_name = LineEditEnvironment()

        self.label_location = LabelBase("Location:")
        self.label_prefix = LabelBase()
        self.button_ok = ButtonPrimary('Clone')
        self.button_cancel = ButtonNormal('Cancel')

        # Widget setup
        self.align_labels([self.label_name, self.label_location])
        self.setMinimumWidth(self.BASE_DIALOG_WIDTH)
        self.setWindowTitle("Clone from environment: " + clone_from_name)
        self.text_name.setPlaceholderText("New environment name")
        self.label_prefix.setObjectName('environment-location')

        # Layouts
        grid = QGridLayout()
        grid.addWidget(self.label_name, 2, 0)
        grid.addWidget(SpacerHorizontal(), 2, 1)
        grid.addWidget(self.text_name, 2, 2)
        grid.addWidget(SpacerVertical(), 3, 0)
        grid.addWidget(self.label_location, 4, 0)
        grid.addWidget(self.label_prefix, 4, 2)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_ok)

        layout = QVBoxLayout()
        layout.addLayout(grid)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_buttons)

        self.setLayout(layout)

        # Signals
        self.text_name.textChanged.connect(self.refresh)
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)

        # Setup
        self.text_name.setFocus()
        self.refresh()

    def refresh(self, text=''):
        """Update status of buttons based on combobox selection."""
        name = self.name
        self.update_location()
        if self.environments:
            self.button_ok.setDisabled(not self.is_valid_env_name(name))
示例#2
0
文件: update.py 项目: amccarty/ac0n3
class DialogUpdateApplication(DialogBase):
    """Update application dialog."""

    WIDTH = 460

    def __init__(self, version, config=CONF, startup=False, qa_testing=False):
        """
        Update application dialog.

        Parameter
        ---------
        version: str
            New version of update available.
        """
        super(DialogUpdateApplication, self).__init__()
        self.tracker = GATracker()

        self.label = QLabel(
            "There's a new version of Anaconda Navigator available. "
            "We strongly recommend you to update. <br><br>"
            "If you click yes, Anaconda Navigator will close and then the "
            "Anaconda Navigator Updater will start.<br><br><br>"
            "Do you wish to update to <b>Anaconda Navigator {0}</b> now?"
            "<br><br>".format(version))
        self.button_yes = ButtonPrimary('Yes')
        self.button_no = ButtonNormal('No, remind me later')
        self.button_no_show = ButtonNormal("No, don't show again")
        self.config = config

        if not startup:
            self.button_no_show.setVisible(False)
            self.button_no.setText('No')

        # Widgets setup
        self.label.setWordWrap(True)
        self.setMinimumWidth(self.WIDTH)
        self.setMaximumWidth(self.WIDTH)
        self.setWindowTitle('Update Application')

        # On QA testing addicon continuumcrew channel allows to test that
        # the update checking mechanism is working with a dummy package
        # version 1000.0.0, this disallows any installation when using that
        # check
        if qa_testing:
            self.button_yes.setDisabled(True)
            self.button_no.setDisabled(True)
            self.button_no_show.setDisabled(True)

        # Layouts
        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_no_show)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_no)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_yes)

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout_buttons.addWidget(SpacerVertical())
        layout_buttons.addWidget(SpacerVertical())
        layout.addLayout(layout_buttons)
        self.setLayout(layout)

        # Signals
        self.button_yes.clicked.connect(self.accept)
        self.button_no.clicked.connect(self.reject)
        self.button_no_show.clicked.connect(self.no_show)

        self.button_yes.setFocus()

    def no_show(self):
        """Handle not showing updates on startup."""
        self.config.set('main', 'hide_update_dialog', True)
        self.reject()
示例#3
0
class ProjectEditor(QWidget):
    sig_dirty_state = Signal(bool)
    sig_saved = Signal()

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

        # Widgets
        self.editor = EditorBase(self)
        self.button_save = ButtonPrimary('Save')
        self.button_problems = ButtonProjectProblems('Problems')
        self.button_suggestions = ButtonProjectSuggestions('Suggestions')
        self.original_text = None
        self.problems = None
        self.suggestions = None

        # Layouts
        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.button_save)
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_problems)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_suggestions)

        layout = QVBoxLayout()
        layout.addLayout(layout_buttons)
        layout.addWidget(self.editor)

        self.setLayout(layout)

        # Signals
        self.editor.textChanged.connect(self.text_changed)
        self.button_save.clicked.connect(self.save)
        self.button_problems.clicked.connect(self.show_problems)
        self.button_suggestions.clicked.connect(self.show_suggestions)

    def show_problems(self):
        """Display problems in a dialog."""
        dlg = DialogProblems(parent=self, problems=self.problems)
        geo_tl = self.button_problems.geometry().topRight()
        tl = self.button_problems.parentWidget().mapToGlobal(geo_tl)
        x = tl.x() - dlg.width()
        y = tl.y() + self.button_problems.height()
        dlg.move(x, y)
        dlg.show()

    def show_suggestions(self):
        """Display suggestions in a dialog."""
        dlg = DialogProblems(parent=self, problems=self.suggestions)
        geo_tl = self.button_suggestions.geometry().topRight()
        tl = self.button_suggestions.parentWidget().mapToGlobal(geo_tl)
        x = tl.x() - dlg.width()
        y = tl.y() + self.button_suggestions.height()
        dlg.move(x, y)
        dlg.show()

    def save(self):
        """Save test to editor."""
        text = self.editor.toPlainText()
        self.original_text = text
        self.button_save.setDisabled(True)
        self.sig_saved.emit()

    def text_changed(self):
        """Callback on text change."""
        dirty = self.is_dirty()
        self.sig_dirty_state.emit(dirty)
        self.button_save.setEnabled(dirty)

    def is_dirty(self):
        """Return if the document is dirty."""
        current_text = self.editor.toPlainText()
        return current_text != self.original_text

    def text(self):
        """Return current plain text from editor."""
        return self.editor.toPlainText()

    def set_info(self, problems, suggestions):
        """Store problems and suggestions for display."""
        self.button_problems.setVisible(False)
        self.button_suggestions.setVisible(False)
        self.problems = None
        self.suggetsions = None

        if problems:
            self.problems = problems
            self.button_problems.setVisible(True)

        if suggestions:
            self.suggestions = suggestions
            self.button_suggestions.setVisible(True)

    def set_text(self, text):
        """Set editor text."""
        self.editor.setPlainText(text)
        self.original_text = text
        self.button_save.setDisabled(True)

    def scroll_value(self):
        """Get scroll value for vertical bar."""
        return self.editor.verticalScrollBar().value()

    def set_scroll_value(self, value):
        """Set scroll value for vertical bar."""
        return self.editor.verticalScrollBar().setValue(value)

    def ordered_widgets(self):
        """Return a list of the ordered widgets."""
        ordered_widgets = [
            self.button_save, self.button_problems, self.button_suggestions,
            self.editor
        ]
        return ordered_widgets
示例#4
0
class PreferencesDialog(DialogBase):
    """Application preferences dialog."""

    sig_urls_updated = Signal(str, str)
    sig_check_ready = Signal()
    sig_reset_ready = Signal()

    def __init__(self, config=CONF, **kwargs):
        """Application preferences dialog."""
        super(PreferencesDialog, self).__init__(**kwargs)

        self.api = AnacondaAPI()
        self.widgets_changed = set()
        self.widgets = []
        self.widgets_dic = {}
        self.config = config

        # Widgets
        self.button_ok = ButtonPrimary('Apply')
        self.button_cancel = ButtonNormal('Cancel')
        self.button_reset = ButtonNormal('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_cancel)
        buttons_layout.addWidget(SpacerHorizontal())
        buttons_layout.addWidget(self.button_ok)

        main_layout = QVBoxLayout()
        main_layout.addLayout(self.grid_layout)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        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):
        """Get configuration option from `main` section."""
        return self.config.get('main', option, None)

    def set_option(self, option, value):
        """Set configuration option in `main` section."""
        self.config.set('main', option, value)

    def get_option_default(self, option):
        """Get configuration option default value in `main` section."""
        return self.config.get_default('main', option)

    def set_option_default(self, option):
        """Set configuration option default value in `main` section."""
        self.set_option(option, self.get_option_default(option))

    def create_widget(
        self,
        widget=None,
        label=None,
        option=None,
        hint=None,
        check=None,
        info=None,
    ):
        """Create preference option widget and add to layout."""
        config_value = self.get_option(option)
        widget._text = label
        widget.label = QLabel(label)
        widget.option = option
        widget.set_value(config_value)
        widget.label_information = QLabel()
        widget.label_information.setMinimumWidth(16)
        widget.label_information.setMaximumWidth(16)

        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)
        h_layout.addWidget(QLabel(hint or ''), 0, Qt.AlignLeft)
        form_widget.setLayout(h_layout)

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

        if info:
            label = widget.label_information
            label = PreferencesDialog.update_icon(label, INFO_ICON)
            label.setToolTip(info)

        self.widgets.append(widget)
        self.widgets_dic[option] = 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, check=None, info=None):
        """Create textbox (QLineEdit) preference option."""
        widget = QLineEdit()
        widget.setAttribute(Qt.WA_MacShowFocusRect, False)
        widget.setMinimumWidth(250)

        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,
            info=info,
        )

    def create_checkbox(self, label, option, check=None, hint=None, info=None):
        """Create checkbox preference option."""
        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)
        )

        api_widget = self.widgets_dic['anaconda_api_url']
        widget.set_warning = lambda w=widget: api_widget
        widget.stateChanged.connect(
            lambda v=None, w=widget: self.options_changed(widget=w)
        )
        self.create_widget(
            widget=widget,
            option=option,
            label=label,
            hint=hint,
            check=check,
            info=info,
        )

    def options_changed(self, value=None, widget=None):
        """Callback helper triggered on preference value change."""
        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)))

    def widget_for_option(self, option):
        """Return the widget for the given option."""
        return self.widgets_dic[option]

    # --- API
    # -------------------------------------------------------------------------
    def set_initial_values(self):
        """
        Set configuration values found in other config files.

        Some options of configuration are found in condarc or in
        anaconda-client configuration.
        """
        self.config.set(
            'main', 'anaconda_api_url', self.api.client_get_api_url()
        )

        # See https://conda.io/docs/install/central.html
        # ssl_verify overloads True/False/<Path to certificate>
        # Navigator splits that into 2 separate options for clarity
        ssl_verify = self.api.client_get_ssl()
        if isinstance(ssl_verify, bool):
            self.config.set('main', 'ssl_verification', ssl_verify)
            self.config.set('main', 'ssl_certificate', None)
        else:
            self.config.set('main', 'ssl_verification', True)
            self.config.set('main', 'ssl_certificate', ssl_verify)

    def setup(self):
        """Setup the preferences dialog."""

        def api_url_checker(value):
            """
            Custom checker to use selected ssl option instead of stored one.

            This allows to set an unsafe api url directly on the preferences
            dialog. Without this, one would have to first disable, click
            accept, then open preferences again and change api url for it to
            work.
            """
            # Ssl widget
            ssl_widget = self.widgets_dic.get('ssl_verification')
            verify = ssl_widget.get_value() if ssl_widget else True

            # Certificate path
            ssl_cert_widget = self.widgets_dic.get('ssl_certificate')
            if ssl_cert_widget:
                verify = ssl_cert_widget.get_value()

            # Offline mode
            offline_widget = self.widgets_dic.get('offline_mode')
            if ssl_widget or ssl_cert_widget:
                offline_mode = offline_widget.get_value()
            else:
                offline_mode = False

            if offline_mode:
                basic_check = (
                    False,
                    'API Domain cannot be modified when '
                    'working in <b>offline mode</b>.<br>',
                )
            else:
                basic_check = self.is_valid_api(value, verify=verify)

            return basic_check

        def ssl_checker(value):
            """Counterpart to api_url_checker."""
            api_url_widget = self.widgets_dic.get('anaconda_api_url')
            api_url = api_url_widget.get_value()
            return self.is_valid_api(api_url, verify=value)

        def ssl_certificate_checker(value):
            """Check if certificate path is valid/exists."""
            ssl_widget = self.widgets_dic.get('ssl_verification')
            verify = ssl_widget.get_value() if ssl_widget else True
            ssl_cert_widget = self.widgets_dic.get('ssl_certificate')
            path = ssl_cert_widget.get_value()
            return self.is_valid_cert_file(path, verify)

        self.set_initial_values()
        self.create_textbox(
            'Anaconda API domain',
            'anaconda_api_url',
            check=api_url_checker,
        )
        self.create_checkbox(
            'Enable SSL verification',
            'ssl_verification',
            check=ssl_checker,
            hint=(
                '<i>Disabling this option is not <br>'
                'recommended for security reasons</i>'
            ),
        )
        self.create_textbox(
            'SSL certificate path (Optional)',
            'ssl_certificate',
            check=ssl_certificate_checker,
        )
        info = '''To help us improve Anaconda Navigator, fix bugs,
and make it even easier for everyone to use Python,
we gather anonymized usage information, just like
most web browsers and mobile apps.'''
        self.create_checkbox(
            'Quality improvement reporting',
            'provide_analytics',
            info=info,
        )
        info_offline = DialogOfflineMode.MESSAGE_PREFERENCES
        extra = '<br><br>' if WIN7 else ''
        self.create_checkbox(
            'Enable offline mode',
            'offline_mode',
            info=info_offline + extra,
        )
        self.create_checkbox('Hide offline mode dialog', 'hide_offline_dialog')
        self.create_checkbox('Hide quit dialog', 'hide_quit_dialog')
        self.create_checkbox(
            'Hide update dialog on startup', 'hide_update_dialog'
        )
        self.create_checkbox(
            'Hide running applications dialog', 'hide_running_apps_dialog'
        )
        self.create_checkbox(
            'Enable high DPI scaling', 'enable_high_dpi_scaling'
        )
        self.create_checkbox(
            'Show application startup error messages', 'show_application_launch_errors'
        )

        ssl_ver_widget = self.widgets_dic.get('ssl_verification')
        ssl_ver_widget.stateChanged.connect(self.enable_disable_cert)
        ssl_cert_widget = self.widgets_dic.get('ssl_certificate')
        ssl_cert_widget.setPlaceholderText(
            'Certificate to verify SSL connections'
        )

        # Refresh enabled/disabled status of certificate textbox
        self.enable_disable_cert()

    def enable_disable_cert(self, value=None):
        """Refresh enabled/disabled status of certificate textbox."""
        ssl_cert_widget = self.widgets_dic.get('ssl_certificate')
        if value:
            value = bool(value)
        else:
            ssl_ver_widget = self.widgets_dic.get('ssl_verification')
            value = bool(ssl_ver_widget.checkState())
        ssl_cert_widget.setEnabled(value)

    @staticmethod
    def update_icon(label, icon):
        """Update icon for information or warning."""
        pixmap = QPixmap(icon)
        label.setScaledContents(True)
        label.setPixmap(
            pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        )
        return label

    @staticmethod
    def warn(widget, text=None):
        """Display warning for widget in preferences."""
        label = widget.label_information
        if text:
            label = PreferencesDialog.update_icon(label, WARNING_ICON)
            label.setToolTip(str(text))
            w = widget.label_information.width() / 2
            h = widget.label_information.height() / 2
            position = widget.label_information.mapToGlobal(QPoint(w, h))
            QCursor.setPos(position)
        else:
            label.setPixmap(QPixmap())
            label.setToolTip('')

    # --- Checkers
    # -------------------------------------------------------------------------
    def is_valid_url(self, url):
        """Check 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 api url.'
        return output, error

    def is_valid_cert_file(self, path, verify):
        """"Check if ssl certificate file in given path exists."""
        output = True
        error = ''

        # Only validate if it is not empty and if ssl_verification is checked
        if path.strip() and verify:
            output = os.path.isfile(path)
            if not output:
                error = 'File not found.'
        return output, error

    def is_valid_api(self, url, verify=True):
        """Check if a given URL is a valid anaconda api endpoint."""
        output = self.api.download_is_valid_api_url(
            url,
            non_blocking=False,
            verify=verify,
        )
        error = ''
        if not output:
            url_api_1 = ''
            url_api_2 = ''

            if '/api' not in url and self.is_valid_url(url)[0]:
                url_api_1 = url.replace('https://', 'https://api.')
                url_api_1 = url_api_1.replace('http://', 'http://api.')

                if url.endswith('/'):
                    url_api_2 = url + 'api'
                else:
                    url_api_2 = url + '/api'

                error = (
                    'Invalid Anaconda API url. <br>'
                    '<br>Try using:<br><b>{0}</b> or <br>'
                    '<b>{1}</b>'.format(url_api_1, url_api_2)
                )
            else:
                error = (
                    'Invalid Anaconda API url.<br><br>'
                    'Check the url is valid and corresponds to the api '
                    'endpoint.'
                )
        return output, error

    def run_checks(self):
        """
        Run all check functions on configuration options.

        This method checks and warns but it does not change/set values.
        """
        checks = []
        for widget in self.widgets_changed:
            value = widget.get_value()
            check, error = widget.check_value(value)
            checks.append(check)

            if check:
                self.warn(widget)
            else:
                self.button_ok.setDisabled(True)
                widget.set_warning()
                self.warn(widget, error)
                break

        # Emit checks ready
        self.sig_check_ready.emit()
        return checks

    def reset_to_defaults(self):
        """Reset the preferences to the default values."""
        for widget in self.widgets:
            option = widget.option
            default = self.get_option_default(option)
            widget.set_value(default)

            # Flag all values as updated
            self.options_changed(widget=widget, value=default)
        self.sig_reset_ready.emit()

    def accept(self):
        """Override Qt method."""
        sig_updated = False
        anaconda_api_url = None
        checks = self.run_checks()

        # Update values
        if checks and all(checks):
            for widget in self.widgets_changed:
                value = widget.get_value()
                self.set_option(widget.option, value)

                # Settings not stored on Navigator config, but taken from
                # anaconda-client config
                if widget.option == 'anaconda_api_url':
                    anaconda_api_url = value  # Store it to be emitted
                    self.api.client_set_api_url(value)
                    sig_updated = True

                # ssl_verify/verify_ssl handles True/False/<Path to cert>
                # On navi it is split in 2 options for clarity
                if widget.option in ['ssl_certificate', 'ssl_verification']:
                    ssl_veri = self.widgets_dic.get('ssl_verification')
                    ssl_cert = self.widgets_dic.get('ssl_certificate')
                    verify = ssl_veri.get_value()
                    path = ssl_cert.get_value()

                    if path.strip() and verify:
                        value = path
                    else:
                        value = verify

                    self.api.client_set_ssl(value)

            if sig_updated and anaconda_api_url:

                def _api_info(worker, output, error):
                    conda_url = output.get('conda_url')
                    try:
                        self.sig_urls_updated.emit(anaconda_api_url, conda_url)
                        super(PreferencesDialog, self).accept()
                    except RuntimeError:
                        # Some tests on appveyor/circleci fail
                        pass

                worker = self.api.api_urls()
                worker.sig_chain_finished.connect(_api_info)

            super(PreferencesDialog, self).accept()
示例#5
0
class AuthenticationDialog(DialogBase):
    """Login dialog."""

    # See https://github.com/Anaconda-Platform/anaconda-server settings
    USER_RE = QRegExp('^[A-Za-z0-9_][A-Za-z0-9_-]+$')
    FORGOT_USERNAME_URL = 'account/forgot_username'
    FORGOT_PASSWORD_URL = 'account/forgot_password'

    sig_authentication_succeeded = Signal()
    sig_authentication_failed = Signal()
    sig_url_clicked = Signal(object)

    def __init__(self, api, parent=None):
        """Login dialog."""
        super(AuthenticationDialog, self).__init__(parent)

        self._parent = parent
        self.config = CONF
        self.api = api
        self.token = None
        self.error = None
        self.tracker = GATracker()
        self.forgot_username_url = None
        self.forgot_password_url = None

        # Widgets
        self.label_username = QLabel('Username:'******'Password:'******'<hr><br><b>Already a member? '
                                        'Sign in!</b><br>')
        # For styling purposes the label next to a ButtonLink is also a button
        # so they align adequately
        self.button_register_text = ButtonLabel('You can register by '
                                                'visiting the ')
        self.button_register = ButtonLink('Anaconda Cloud')
        self.button_register_after_text = ButtonLabel('website.')
        self.label_information = QLabel('''
            <strong>Anaconda Cloud</strong> is where packages, notebooks,
            and <br> environments are shared. It provides powerful <br>
            collaboration and package management for open <br>
            source and private projects.<br>
            ''')
        self.label_message = QLabel('')
        self.button_forgot_username = ButtonLink('I forgot my username')
        self.button_forgot_password = ButtonLink('I forgot my password')
        self.button_login = ButtonPrimary('Login')
        self.button_cancel = ButtonNormal('Cancel')

        # Widgets setup
        self.button_login.setDefault(True)
        username_validator = QRegExpValidator(self.USER_RE)
        self.text_username.setValidator(username_validator)

        self.setMinimumWidth(260)
        self.setWindowTitle('Sign in')

        # This allows to completely style the dialog with css using the frame
        self.text_password.setEchoMode(QLineEdit.Password)
        self.label_message.setVisible(False)

        # Layout
        grid_layout = QVBoxLayout()
        grid_layout.addWidget(self.label_username)
        # grid_layout.addWidget(SpacerVertical())
        grid_layout.addWidget(self.text_username)
        grid_layout.addWidget(SpacerVertical())
        grid_layout.addWidget(self.label_password)
        # grid_layout.addWidget(SpacerVertical())
        grid_layout.addWidget(self.text_password)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.label_information)

        register_layout = QHBoxLayout()
        register_layout.addWidget(self.button_register_text)
        register_layout.addWidget(self.button_register)
        register_layout.addWidget(self.button_register_after_text)
        register_layout.addStretch()
        main_layout.addLayout(register_layout)
        main_layout.addWidget(self.label_signin_text)
        main_layout.addLayout(grid_layout)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(self.label_message)
        main_layout.addWidget(self.button_forgot_username, 0, Qt.AlignRight)
        main_layout.addWidget(self.button_forgot_password, 0, Qt.AlignRight)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_login)

        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        main_layout.addLayout(layout_buttons)

        self.setLayout(main_layout)

        # Signals
        self.text_username.textEdited.connect(self.check_text)
        self.text_password.textEdited.connect(self.check_text)
        self.button_login.clicked.connect(self.login)
        self.button_cancel.clicked.connect(self.reject)

        # Setup
        self.check_text()
        self.update_style_sheet()
        self.text_username.setFocus()
        self.setup()

    def setup(self):
        """Setup login dialog."""
        self.update_links()

    def update_links(self):
        """Update links."""
        for button in [
                self.button_forgot_username,
                self.button_forgot_password,
                self.button_register,
        ]:
            try:
                button.disconnect()
            except TypeError:  # pragma: no cover
                pass

        # TODO, get this from anaconda client directly?
        # from binstar_client.utils import get_config, set_config
        # config = get_config()
        anaconda_api_url = self.config.get('main', 'anaconda_api_url', None)
        if anaconda_api_url:
            # Remove api if using a subdomain
            base_url = anaconda_api_url.lower().replace('//api.', '//')
            self.base_url = base_url

            # Remove api if not using a subdomain
            parts = base_url.lower().split('/')
            if parts[-1] == 'api':
                base_url = '/'.join(parts[:-1])

            self.forgot_username_url = (base_url + '/' +
                                        self.FORGOT_USERNAME_URL)
            self.forgot_password_url = (base_url + '/' +
                                        self.FORGOT_PASSWORD_URL)

            self.button_register.clicked.connect(
                lambda: self.open_url(base_url))
            self.button_forgot_username.clicked.connect(
                lambda: self.open_url(self.forgot_username_url))
            self.button_forgot_password.clicked.connect(
                lambda: self.open_url(self.forgot_password_url))

    @property
    def username(self):
        """Return the logged username."""
        return self.text_username.text().lower()

    def update_style_sheet(self, style_sheet=None):
        """Update custom css style sheet."""
        if style_sheet is None:
            style_sheet = load_style_sheet()
        self.setStyleSheet(style_sheet)

    def check_text(self):
        """Check that `username` and `password` are valid.

        If not empty and disable/enable buttons accordingly.
        """
        username = self.text_username.text()
        password = self.text_password.text()

        if len(username) == 0 or len(password) == 0:
            self.button_login.setDisabled(True)
        else:
            self.button_login.setDisabled(False)

    def login(self):
        """Try to log the user in the specified anaconda api endpoint."""
        self.button_login.setEnabled(False)
        self.text_username.setText(self.text_username.text().lower())
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.label_message.setText('')
        worker = self.api.login(self.text_username.text().lower(),
                                self.text_password.text())
        worker.sig_finished.connect(self._finished)

    def _finished(self, worker, output, error):
        """Callback for the login procedure after worker has finished."""
        token = output
        if token:
            self.token = token
            self.sig_authentication_succeeded.emit()
            self.accept()
        elif error:
            username = self.text_username.text().lower()
            bold_username = '******'.format(username)

            # The error might come in (error_message, http_error) format
            try:
                error_message = ast.literal_eval(str(error))[0]
            except Exception:  # pragma: no cover
                error_message = str(error)

            error_message = error_message.lower().capitalize()
            error_message = error_message.split(', ')[0]
            error_text = '<i>{0}</i>'.format(error_message)
            error_text = error_text.replace(username, bold_username)
            self.label_message.setText(error_text)
            self.label_message.setVisible(True)

            if error_message:
                domain = self.api.client_domain()
                label = '{0}/{1}: {2}'.format(domain, username,
                                              error_message.lower())
                self.tracker.track_event(
                    'authenticate',
                    'login failed',
                    label=label,
                )
                self.text_password.setFocus()
                self.text_password.selectAll()
            self.sig_authentication_failed.emit()

        self.button_login.setDisabled(False)
        self.check_text()
        QApplication.restoreOverrideCursor()

    def open_url(self, url):
        """Open given url in the default browser and log the action."""
        self.tracker.track_event('content', 'click', url)
        self.sig_url_clicked.emit(url)
        QDesktopServices.openUrl(QUrl(url))
示例#6
0
class ImportDialog(DialogBase):
    """Import project from folder or specification files."""

    CONDA_ENV_FILES = 'Conda environment files (*.yaml *.yml)'
    CONDA_SPEC_FILES = 'Conda explicit specification files (*.txt)'
    PIP_REQUIREMENT_FILES = 'Pip requirement files (*.txt)'

    def __init__(self, parent=None, projects=None):
        """Import project from folder or environment files."""
        super(ImportDialog, self).__init__(parent=parent)

        self.projects = projects if projects else {}
        self.selected_file_filter = None
        self._path = None

        # Widgets
        self.label_info = LabelSpecInfo('', parent=self)
        self.label_name = QLabel("Project name")
        self.label_path = QLabel("Specification File")
        self.text_name = QLineEdit()
        self.text_path = QLineEdit()
        self.button_path = ButtonNormal("")
        self.radio_folder = QRadioButton('From folder')
        self.radio_spec = QRadioButton('From specification file')
        self.button_cancel = ButtonNormal('Cancel')
        self.button_ok = ButtonPrimary('Import')

        # Widgets setup
        self.button_path.setObjectName('import')
        self.button_ok.setDefault(True)
        self.text_path.setPlaceholderText("File to import from")
        self.text_name.setPlaceholderText("New project name")
        self.setMinimumWidth(380)
        self.setWindowTitle("Import new project")
        self.text_name.setValidator(get_regex_validator())

        # Layouts
        layout_radio = QHBoxLayout()
        layout_radio.addWidget(self.radio_folder)
        layout_radio.addWidget(SpacerHorizontal())
        layout_radio.addWidget(self.radio_spec)

        layout_infile = QHBoxLayout()
        layout_infile.addWidget(self.text_path)
        layout_infile.addWidget(SpacerHorizontal())
        layout_infile.addWidget(self.button_path)

        layout_grid = QGridLayout()
        layout_grid.addWidget(self.label_name, 0, 0, 1, 2)
        layout_grid.addWidget(SpacerHorizontal(), 0, 2)
        layout_grid.addWidget(self.text_name, 0, 3)
        layout_grid.addWidget(SpacerVertical(), 1, 0)
        layout_grid.addWidget(self.label_path, 2, 0)
        layout_grid.addWidget(self.label_info, 2, 1)
        layout_grid.addWidget(SpacerHorizontal(), 2, 2)
        layout_grid.addLayout(layout_infile, 2, 3)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_ok)

        layout = QVBoxLayout()
        layout.addLayout(layout_radio)
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_grid)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_buttons)

        self.setLayout(layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.button_path.clicked.connect(self.choose)
        self.text_path.textChanged.connect(self.refresh)
        self.text_name.textChanged.connect(self.refresh)
        self.radio_folder.toggled.connect(self.refresh)
        self.radio_spec.toggled.connect(self.refresh)

        # Setup
        self.radio_folder.toggle()
        self.refresh()

    def refresh(self, text=''):
        """Update the status of buttons based on radio selection."""
        if self.radio_folder.isChecked():
            self.text_path.setPlaceholderText("Folder to import from")
            self.label_path.setText('Folder')
            self.label_info.setVisible(False)
        else:
            self.label_info.setVisible(True)
            self.label_path.setText('File ')
            self.text_path.setPlaceholderText("File to import from")

        text = self.text_name.text()
        path = self.text_path.text()

        if (text and path and os.path.exists(path)
                and is_valid_project_name(text, self.projects)):
            self.button_ok.setDisabled(False)
            self.button_ok.setDefault(True)
        else:
            self.button_ok.setDisabled(True)
            self.button_cancel.setDefault(True)

    def choose(self):
        """Display file dialog to select environment specification."""
        selected_filter = None
        if self.radio_spec.isChecked():
            path, selected_filter = getopenfilename(
                caption="Import Project",
                basedir=HOME_PATH,
                parent=None,
                filters="{0};;{1};;{2}".format(self.CONDA_ENV_FILES,
                                               self.CONDA_SPEC_FILES,
                                               self.PIP_REQUIREMENT_FILES))
        else:
            path = getexistingdirectory(
                caption="Import Project",
                basedir=HOME_PATH,
                parent=None,
            )

        if path:
            name = self.text_name.text()
            self.selected_file_filter = selected_filter
            self.text_path.setText(path)
            self.refresh(path)
            self.text_name.setText(name)

    @property
    def name(self):
        """Return the project name."""
        return self.text_name.text().strip()

    @property
    def path(self):
        """Return the project path to import (file or folder)."""
        return self.text_path.text()
示例#7
0
class CreateDialog(DialogBase):
    """Create new project dialog."""
    def __init__(self, parent=None, projects=None):
        """Create new environment dialog."""
        super(CreateDialog, self).__init__(parent=parent)

        self.projects = projects

        # Widgets
        self.label_name = QLabel("Project name")
        self.text_name = QLineEdit()
        self.button_ok = ButtonPrimary('Create')
        self.button_cancel = ButtonNormal('Cancel')

        # Widgets setup
        self.text_name.setPlaceholderText("New project name")
        self.setMinimumWidth(380)
        self.setWindowTitle("Create new project")
        self.text_name.setValidator(get_regex_validator())

        # Layouts
        grid = QGridLayout()
        grid.addWidget(self.label_name, 0, 0)
        grid.addWidget(SpacerHorizontal(), 0, 1)
        grid.addWidget(self.text_name, 0, 2)
        grid.addWidget(SpacerVertical(), 1, 0)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_ok)

        main_layout = QVBoxLayout()
        main_layout.addLayout(grid)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        main_layout.addLayout(layout_buttons)

        self.setLayout(main_layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.text_name.textChanged.connect(self.refresh)

        # Setup
        self.refresh()

    def refresh(self, text=''):
        """Update status of buttons based on combobox selection."""
        self.button_ok.setDisabled(True)
        text = self.text_name.text().strip()
        if self.projects is not None:
            if is_valid_project_name(text, self.projects):
                self.button_ok.setDisabled(False)
            else:
                self.button_ok.setDisabled(True)

    @property
    def name(self):
        """Return the project name."""
        return self.text_name.text().strip()
示例#8
0
class DialogChannels(DialogBase):
    """Dialog to add delete and select active conda package channels."""

    sig_channels_updated = Signal(object, object)  # added, removed
    sig_setup_ready = Signal()
    sig_check_ready = Signal()
    WIDTH = 550

    def __init__(self, parent=None):
        """Dialog to add delete and select active conda pacakge channels ."""
        super(DialogChannels, self).__init__(parent)
        self._parent = parent
        self._conda_url = 'https://conda.anaconda.org'
        self.api = AnacondaAPI()
        self.initial_sources = None
        self.config_sources = None
        self.style_sheet = None
        self._setup_ready = False
        self._conda_url_setup_ready = False

        # Widgets
        self.list = ListWidgetChannels(parent=self, api=self.api)
        self.label_info = LabelBase(
            'Manage channels you want Navigator to include.')
        self.label_status = LabelBase('Collecting sources...')
        self.progress_bar = QProgressBar(self)
        self.button_add = ButtonNormal('Add...')
        self.button_cancel = ButtonNormal('Cancel')
        self.button_ok = ButtonPrimary('Update channels')

        # Widget setup
        self.frame_title_bar.setVisible(False)
        self.list.setFrameStyle(QFrame.NoFrame)
        self.list.setFrameShape(QFrame.NoFrame)
        self.setWindowFlags(self.windowFlags() | Qt.Popup)
        self.setWindowOpacity(0.96)
        self.setMinimumHeight(300)
        self.setMinimumWidth(self.WIDTH)
        self.setModal(True)

        # Layout
        layout_button = QHBoxLayout()
        layout_button.addWidget(self.label_info)
        layout_button.addStretch()
        layout_button.addWidget(self.button_add)

        layout_ok = QHBoxLayout()
        layout_ok.addWidget(self.label_status)
        layout_ok.addWidget(SpacerHorizontal())
        layout_ok.addWidget(self.progress_bar)
        layout_ok.addWidget(SpacerHorizontal())
        layout_ok.addStretch()
        layout_ok.addWidget(self.button_cancel)
        layout_ok.addWidget(SpacerHorizontal())
        layout_ok.addWidget(self.button_ok)

        layout = QVBoxLayout()
        layout.addLayout(layout_button)
        layout.addWidget(SpacerVertical())
        layout.addWidget(self.list)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_ok)
        self.setLayout(layout)

        # Signals
        self.button_add.clicked.connect(self.add_channel)
        self.button_ok.clicked.connect(self.update_channels)
        self.button_cancel.clicked.connect(self.reject)
        self.list.sig_status_updated.connect(self.update_status)
        self.list.sig_channel_added.connect(
            lambda v=None: self.set_tab_order())
        self.list.sig_channel_added.connect(
            lambda v=None: self.button_ok.setFocus())
        self.list.sig_channel_removed.connect(
            lambda v=None: self.set_tab_order())
        self.list.sig_channel_removed.connect(
            lambda v=None: self.button_ok.setFocus())
        self.list.sig_channel_checked.connect(self.sig_check_ready)
        self.list.sig_channel_status.connect(self.refresh)

        self.button_add.setDisabled(True)
        self.button_ok.setDisabled(True)
        self.button_cancel.setDisabled(True)
        self.update_status(action='Collecting sources...',
                           value=0,
                           max_value=0)

    @staticmethod
    def _group_sources_and_channels(sources):
        """
        Flatten sources and channels dictionary to list of tuples.

        [(source, channel), (source, channel)...]
        """
        grouped = []
        for source, channels in sources.items():
            for channel in channels:
                grouped.append((source, channel))
        return grouped

    def keyPressEvent(self, event):
        """Override Qt method."""
        key = event.key()
        if key in [Qt.Key_Escape]:
            if self.list.is_editing:
                self.refresh()
                self.list.is_editing = False
            else:
                self.reject()

    # --- Public API
    # -------------------------------------------------------------------------
    def update_style_sheet(self, style_sheet=None):
        """Update custom css style sheets."""
        if style_sheet is None:
            self.style_sheet = load_style_sheet()
        else:
            self.style_sheet = style_sheet

        self.setStyleSheet(self.style_sheet)
        self.setMinimumWidth(SASS_VARIABLES.WIDGET_CHANNEL_DIALOG_WIDTH)

        try:
            self.list.update_style_sheet(style_sheet)
        except Exception:
            pass

    def update_api(self, worker, api_info, error):
        """Update api info."""
        self._conda_url = api_info.get('conda_url',
                                       'https://conda.anaconda.org')
        self._conda_url_setup_ready = True

        if self._setup_ready:
            self.sig_setup_ready.emit()

    def setup(self, worker, conda_config_data, error):
        """Setup the channels widget."""
        self.config_sources = conda_config_data.get('config_sources')
        self.button_add.setDisabled(False)

        for source, data in self.config_sources.items():
            channels = data.get('channels', [])
            for channel in channels:
                item = ListWidgetItemChannel(channel=channel, location=source)
                item.set_editable(False)
                self.list.addItem(item)

        self.set_tab_order()
        self.button_add.setFocus()
        self.button_ok.setDefault(True)
        self.button_cancel.setEnabled(True)

        self.initial_sources = self.list.sources.copy()
        self.update_status()
        self._setup_ready = True

        if self._conda_url_setup_ready:
            self.sig_setup_ready.emit()

    def set_tab_order(self):
        """Fix the tab ordering in the list."""
        if self.list._items:
            self.setTabOrder(self.button_add,
                             self.list._items[0].button_remove)
            self.setTabOrder(self.list._items[-1].button_remove,
                             self.button_cancel)

        self.setTabOrder(self.button_cancel, self.button_ok)
        self.refresh()

    def add_channel(self):
        """Add new conda channel."""
        user_rc_path = self.api._conda_api.user_rc_path
        item = ListWidgetItemChannel(channel='', location=user_rc_path)
        self.list.addItem(item)
        self.refresh(False)

    def update_channels(self):
        """Update channels list and status."""
        sources = self.list.sources

        original = self._group_sources_and_channels(self.initial_sources)
        updated = self._group_sources_and_channels(sources)

        if sorted(original) != sorted(updated):
            self.sig_channels_updated.emit(*self.sources)
            self.accept()
        else:
            self.reject()

    def refresh(self, channel_status=True):
        """Update enable/disable status based on item count."""
        self.button_add.setEnabled(channel_status and bool(self.list.count))
        self.button_ok.setEnabled(channel_status)
        self.button_cancel.setEnabled(True)

        if self.list.count() == 0:
            self.button_add.setEnabled(True)
            self.button_ok.setEnabled(False)

    def update_status(self, action='', message='', value=None, max_value=None):
        """Update the status and progress bar of the widget."""
        visible = bool(action)
        self.label_status.setText(action)
        self.label_status.setVisible(visible)
        if value is not None and max_value is not None:
            self.progress_bar.setVisible(True)
            self.progress_bar.setRange(0, max_value)
            self.progress_bar.setValue(value)
        else:
            self.progress_bar.setVisible(False)

    @property
    def sources(self):
        """Return sources to add and remove from config."""
        original = self._group_sources_and_channels(self.initial_sources)
        updated = self._group_sources_and_channels(self.list.sources)

        original = set(original)
        updated = set(updated)

        add = updated - original
        remove = original - updated

        return add, remove
示例#9
0
class PackagesDialog(DialogBase):
    """Package dependencies dialog."""

    sig_setup_ready = Signal()

    def __init__(
        self,
        parent=None,
        packages=None,
        pip_packages=None,
        remove_only=False,
        update_only=False,
    ):
        """About dialog."""
        super(PackagesDialog, self).__init__(parent=parent)

        # Variables
        self.api = AnacondaAPI()
        self.actions = None
        self.packages = packages or []
        self.pip_packages = pip_packages or []

        # Widgets
        self.stack = QStackedWidget()
        self.table = QTableWidget()
        self.text = QTextEdit()
        self.label_description = LabelBase()
        self.label_status = LabelBase()
        self.progress_bar = QProgressBar()
        self.button_ok = ButtonPrimary('Apply')
        self.button_cancel = ButtonNormal('Cancel')

        # Widget setup
        self.text.setReadOnly(True)
        self.stack.addWidget(self.table)
        self.stack.addWidget(self.text)
        if remove_only:
            text = 'The following packages will be removed:<br>'
        else:
            text = 'The following packages will be modified:<br>'
        self.label_description.setText(text)
        self.label_description.setWordWrap(True)
        self.label_description.setWordWrap(True)
        self.label_status.setWordWrap(True)
        self.table.horizontalScrollBar().setVisible(False)
        self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.table.setAlternatingRowColors(True)
        self.table.setSelectionMode(QAbstractItemView.NoSelection)
        self.table.setSortingEnabled(True)
        self._hheader = self.table.horizontalHeader()
        self._vheader = self.table.verticalHeader()
        self._hheader.setStretchLastSection(True)
        self._hheader.setDefaultAlignment(Qt.AlignLeft)
        self._hheader.setSectionResizeMode(self._hheader.Fixed)
        self._vheader.setSectionResizeMode(self._vheader.Fixed)
        self.button_ok.setMinimumWidth(70)
        self.button_ok.setDefault(True)
        self.base_minimum_width = 300 if remove_only else 420
        if remove_only:
            self.setWindowTitle("Remove Packages")
        elif update_only:
            self.setWindowTitle("Update Packages")
        else:
            self.setWindowTitle("Install Packages")

        self.setMinimumWidth(self.base_minimum_width)

        # Layouts
        layout_progress = QHBoxLayout()
        layout_progress.addWidget(self.label_status)
        layout_progress.addWidget(SpacerHorizontal())
        layout_progress.addWidget(self.progress_bar)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_ok)

        layout = QVBoxLayout()
        layout.addWidget(self.label_description)
        layout.addWidget(SpacerVertical())
        layout.addWidget(self.stack)
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_progress)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_buttons)
        self.setLayout(layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.button_ok.setDisabled(True)

        # Setup
        self.table.setDisabled(True)
        self.update_status('Solving package specifications',
                           value=0,
                           max_value=0)

    def setup(self, worker, output, error):
        """Setup the widget to include the list of dependencies."""
        if not isinstance(output, dict):
            output = {}

        packages = sorted(pkg.split('==')[0] for pkg in self.packages)
        success = output.get('success')
        error = output.get('error', '')
        exception_name = output.get('exception_name', '')
        actions = output.get('actions', [])
        prefix = worker.prefix

        if exception_name:
            message = exception_name
        else:
            # All requested packages already installed
            message = output.get('message', ' ')

        navi_deps_error = self.api.check_navigator_dependencies(
            actions, prefix)
        description = self.label_description.text()

        if error:
            description = 'No packages will be modified.'
            self.stack.setCurrentIndex(1)
            self.button_ok.setDisabled(True)
            if self.api.is_offline():
                error = ("Some of the functionality of Anaconda Navigator "
                         "will be limited in <b>offline mode</b>. <br><br>"
                         "Installation and upgrade actions will be subject to "
                         "the packages currently available on your package "
                         "cache.")
            self.text.setText(error)
        elif navi_deps_error:
            description = 'No packages will be modified.'
            error = ('Downgrading/removing these packages will modify '
                     'Anaconda Navigator dependencies.')
            self.text.setText(error)
            self.stack.setCurrentIndex(1)
            message = 'NavigatorDependenciesError'
            self.button_ok.setDisabled(True)
        elif success and actions:
            self.stack.setCurrentIndex(0)
            # Conda 4.3.x
            if isinstance(actions, list):
                actions_link = actions[0].get('LINK', [])
                actions_unlink = actions[0].get('UNLINK', [])
            # Conda 4.4.x
            else:
                actions_link = actions.get('LINK', [])
                actions_unlink = actions.get('UNLINK', [])

            deps = set()
            deps = deps.union({p['name'] for p in actions_link})
            deps = deps.union({p['name'] for p in actions_unlink})
            deps = deps - set(packages)
            deps = sorted(list(deps))

            count_total_packages = len(packages) + len(deps)
            plural_total = 's' if count_total_packages != 1 else ''
            plural_selected = 's' if len(packages) != 1 else ''

            self.table.setRowCount(count_total_packages)
            self.table.setColumnCount(4)
            if actions_link:
                description = '{0} package{1} will be installed'.format(
                    count_total_packages, plural_total)
                self.table.showColumn(2)
                self.table.showColumn(3)
            elif actions_unlink and not actions_link:
                self.table.hideColumn(2)
                self.table.hideColumn(3)
                self.table.setHorizontalHeaderLabels(
                    ['Name', 'Unlink', 'Link', 'Channel'])
                description = '{0} package{1} will be removed'.format(
                    count_total_packages, plural_total)

            for row, pkg in enumerate(packages + deps):
                link_item = [p for p in actions_link if p['name'] == pkg]
                if not link_item:
                    link_item = {
                        'version': '-'.center(len('link')),
                        'channel': '-'.center(len('channel')),
                    }
                else:
                    link_item = link_item[0]

                unlink_item = [p for p in actions_unlink if p['name'] == pkg]
                if not unlink_item:
                    unlink_item = {
                        'version': '-'.center(len('link')),
                    }
                else:
                    unlink_item = unlink_item[0]

                unlink_version = str(unlink_item['version'])
                link_version = str(link_item['version'])

                item_unlink_v = QTableWidgetItem(unlink_version)
                item_link_v = QTableWidgetItem(link_version)
                item_link_c = QTableWidgetItem(link_item['channel'])
                if pkg in packages:
                    item_name = QTableWidgetItem(pkg)
                else:
                    item_name = QTableWidgetItem('*' + pkg)

                items = [item_name, item_unlink_v, item_link_v, item_link_c]
                for column, item in enumerate(items):
                    item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
                    self.table.setItem(row, column, item)

            if deps:
                message = (
                    '<b>*</b> indicates the package is a dependency of a '
                    'selected package{0}<br>').format(plural_selected)

            self.button_ok.setEnabled(True)
            self.table.resizeColumnsToContents()
            unlink_width = self.table.columnWidth(1)
            if unlink_width < 60:
                self.table.setColumnWidth(1, 60)
            self.table.setHorizontalHeaderLabels(
                ['Name  ', 'Unlink  ', 'Link  ', 'Channel  '])

        self.table.setEnabled(True)
        self.update_status(message=message)
        self.label_description.setText(description)

        # Adjust size after data has populated the table
        self.table.resizeColumnsToContents()
        width = sum(
            self.table.columnWidth(i) for i in range(self.table.columnCount()))
        delta = (self.width() - self.table.width() +
                 self.table.verticalHeader().width() + 10)

        new_width = width + delta

        if new_width < self.base_minimum_width:
            new_width = self.base_minimum_width

        self.setMinimumWidth(new_width)
        self.setMaximumWidth(new_width)

        self.sig_setup_ready.emit()

    def update_status(self, message='', value=None, max_value=None):
        """Update status of packages dialog."""
        self.label_status.setText(message)

        if max_value is None and value is None:
            self.progress_bar.setVisible(False)
        else:
            self.progress_bar.setVisible(True)
            self.progress_bar.setMaximum(max_value)
            self.progress_bar.setValue(value)
示例#10
0
class ConflictDialog(EnvironmentActionsDialog):
    """Create new environment dialog if navigator conflicts with deps."""
    def __init__(self,
                 parent=None,
                 package=None,
                 extra_message='',
                 current_prefix=None):
        """Create new environment dialog if navigator conflicts with deps."""
        super(ConflictDialog, self).__init__(parent=parent)

        parts = package.split('=')
        self.package = parts[0] if '=' in package else package
        self.package_version = parts[-1] if '=' in package else ''
        self.current_prefix = current_prefix

        base_message = ('<b>{0}</b> cannot be installed on this '
                        'environment.').format(package)

        base_message = extra_message or base_message
        # Widgets
        self.label_info = LabelBase(
            base_message + '<br><br>'
            'Do you want to install the package in an existing '
            'environment or <br>create a new environment?'
            ''.format(package))
        self.label_name = LabelBase('Name:')
        self.label_prefix = LabelBase(' ' * 100)
        self.label_location = LabelBase('Location:')
        self.combo_name = ComboBoxBase()
        self.button_ok = ButtonPrimary('Create')
        self.button_cancel = ButtonNormal('Cancel')

        # Widgets setup
        self.align_labels([self.label_name, self.label_location])
        self.combo_name.setEditable(True)
        self.combo_name.setCompleter(None)
        self.combo_name.setValidator(self.get_regex_validator())
        self.setMinimumWidth(self.BASE_DIALOG_WIDTH)
        self.setWindowTitle("Create new environment for '{}'".format(package))
        self.label_prefix.setObjectName('environment-location')
        self.combo_name.setObjectName('environment-selection')

        # Layouts
        grid_layout = QGridLayout()
        grid_layout.addWidget(self.label_name, 0, 0)
        grid_layout.addWidget(SpacerHorizontal(), 0, 1)
        grid_layout.addWidget(self.combo_name, 0, 2)
        grid_layout.addWidget(SpacerVertical(), 1, 0)
        grid_layout.addWidget(self.label_location, 2, 0)
        grid_layout.addWidget(SpacerHorizontal(), 2, 1)
        grid_layout.addWidget(self.label_prefix, 2, 2)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_ok)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.label_info)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        main_layout.addLayout(grid_layout)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        main_layout.addLayout(layout_buttons)

        self.setLayout(main_layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.combo_name.setCurrentText(self.package)
        self.combo_name.currentTextChanged.connect(self.refresh)
        self.button_ok.setDisabled(True)

    def new_env_name(self):
        """Generate a unique environment name."""
        pkg_name_version = self.package + '-' + self.package_version
        if self.environments:
            if self.package not in self.environments.values():
                env_name = self.package
            elif pkg_name_version not in self.environments.values():
                env_name = pkg_name_version
            else:
                for i in range(1, 1000):
                    new_pkg_name = pkg_name_version + '_' + str(i)
                    if new_pkg_name not in self.environments.values():
                        env_name = new_pkg_name
                        break
        else:
            env_name = self.package
        return env_name

    def setup(self, worker, info, error):
        """Setup the dialog conda information as a result of a worker."""
        super(ConflictDialog, self).setup(worker, info, error)
        self.combo_name.blockSignals(True)
        self.combo_name.clear()
        new_env_name = self.new_env_name()
        for i, (env_prefix, env_name) in enumerate(self.environments.items()):
            # Do not include the env where the conflict was found!
            if self.current_prefix != env_prefix:
                self.combo_name.addItem(env_name, env_prefix)
                self.combo_name.setCurrentText(new_env_name)
                self.combo_name.setItemData(i, env_prefix, Qt.ToolTipRole)
        self.combo_name.setCurrentText(new_env_name)
        self.combo_name.blockSignals(False)
        self.refresh()

    def refresh(self):
        """Refresh state of buttons based on content."""
        self.update_location()

        if self.environments:
            self.button_ok.setEnabled(bool(self.name))

    @property
    def name(self):
        """Return the name of the environment."""
        return self.combo_name.currentText()
示例#11
0
class ImportDialog(EnvironmentActionsDialog):
    """Import environment from environment specification dialog."""

    CONDA_ENV_FILES = 'Conda environment files (*.yaml *.yml)'
    CONDA_SPEC_FILES = 'Conda explicit specification files (*.txt)'
    PIP_REQUIREMENT_FILES = 'Pip requirement files (*.txt)'

    def __init__(self, parent=None):
        """Import environment from environment specification dialog."""
        super(ImportDialog, self).__init__(parent=parent)

        self.environments = None
        self.env_dirs = None
        self.selected_file_filter = None

        # Widgets
        self.label_name = LabelBase("Name:")
        self.label_location = LabelBase("Location:")
        self.label_path = LabelBase("Specification File")
        self.text_name = LineEditBase()
        self.label_prefix = LabelBase("")
        self.text_path = LineEditBase()
        self.button_path = ButtonNormal("")
        self.button_cancel = ButtonNormal('Cancel')
        self.button_ok = ButtonPrimary('Import')

        # Widgets setup
        self.align_labels(
            [self.label_name, self.label_location, self.label_path])
        self.label_prefix.setObjectName('environment-location')
        self.button_path.setObjectName('import')
        self.button_ok.setDefault(True)
        self.text_path.setPlaceholderText("File to import from")
        self.text_name.setPlaceholderText("New environment name")
        self.setMinimumWidth(self.BASE_DIALOG_WIDTH)
        self.setWindowTitle("Import new environment")
        self.text_name.setValidator(self.get_regex_validator())

        # Layouts
        layout_infile = QHBoxLayout()
        layout_infile.addWidget(self.text_path)
        layout_infile.addWidget(SpacerHorizontal())
        layout_infile.addWidget(self.button_path)

        layout_grid = QGridLayout()
        layout_grid.addWidget(self.label_name, 0, 0)
        layout_grid.addWidget(SpacerHorizontal(), 0, 1)
        layout_grid.addWidget(self.text_name, 0, 2)
        layout_grid.addWidget(SpacerVertical(), 1, 0)
        layout_grid.addWidget(self.label_location, 2, 0)
        layout_grid.addWidget(SpacerHorizontal(), 2, 1)
        layout_grid.addWidget(self.label_prefix, 2, 2)
        layout_grid.addWidget(SpacerVertical(), 3, 0)
        layout_grid.addWidget(self.label_path, 4, 0)
        layout_grid.addWidget(SpacerHorizontal(), 4, 1)
        layout_grid.addLayout(layout_infile, 4, 2)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_ok)

        layout = QVBoxLayout()
        layout.addLayout(layout_grid)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_buttons)

        self.setLayout(layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.button_path.clicked.connect(self.choose)
        self.text_path.textChanged.connect(self.refresh)
        self.text_name.textChanged.connect(self.refresh)

        # Setup
        self.text_name.setFocus()
        self.refresh()

    def choose(self):
        """Display file dialog to select environment specification."""
        path, selected_filter = getopenfilename(
            caption="Import Environment",
            basedir=os.path.expanduser('~'),
            parent=None,
            filters="{0};;{1};;{2}".format(self.CONDA_ENV_FILES,
                                           self.CONDA_SPEC_FILES,
                                           self.PIP_REQUIREMENT_FILES))

        if path:
            name = self.name
            self.selected_file_filter = selected_filter
            self.text_path.setText(path)
            self.refresh(path)

            # Try to get the name key of the file if an environment.yaml file
            if selected_filter == self.CONDA_ENV_FILES:
                try:
                    with open(path, 'r') as f:
                        raw = f.read()
                    data = yaml.load(raw)
                    name = data.get('name', name)
                except Exception:
                    pass
            self.text_name.setText(name)

    def refresh(self, text=''):
        """Update the status of buttons based data entered."""
        name = self.name
        path = self.path

        self.update_location()

        if (name and path and os.path.exists(self.path)
                and self.is_valid_env_name(name)):
            self.button_ok.setDisabled(False)
            self.button_ok.setDefault(True)
        else:
            self.button_ok.setDisabled(True)
            self.button_cancel.setDefault(True)

    @property
    def path(self):
        """Return the content of the selected path if it exists."""
        path = None
        if os.path.isfile(self.text_path.text()):
            path = self.text_path.text()
        return path
示例#12
0
class CreateDialog(EnvironmentActionsDialog):
    """Create new environment dialog."""

    PYTHON_VERSIONS = ['2.7', '3.5', '3.6']

    def __init__(self, parent=None):
        """Create new environment dialog."""
        super(CreateDialog, self).__init__(parent=parent)

        # Widgets
        self.label_name = LabelBase("Name:")
        self.label_location = LabelBase("Location:")
        self.label_prefix = LabelBase('')
        self.text_name = LineEditBase()
        self.label_version = LabelBase("Python version")
        self.label_packages = LabelBase("Packages:")
        self.combo_version = ComboBoxBase()
        self.check_python = CheckBoxBase("Python")
        self.check_r = CheckBoxBase('R')
        self.button_ok = ButtonPrimary('Create')
        self.button_cancel = ButtonNormal('Cancel')

        # Widgets setup
        self.align_labels(
            [self.label_name, self.label_location, self.label_packages])
        self.text_name.setPlaceholderText("New environment name")
        self.setMinimumWidth(self.BASE_DIALOG_WIDTH)
        self.setWindowTitle("Create new environment")
        self.text_name.setValidator(self.get_regex_validator())
        self.label_prefix.setObjectName('environment-location')
        self.combo_version.setObjectName('package-version')

        # Supported set of python versions
        versions = self.PYTHON_VERSIONS
        now = "{}.{}".format(sys.version_info.major, sys.version_info.minor)
        if now not in versions:
            # Guard against non-standard version, or the coming 3.6
            versions.append(now)
            versions.sort()
        versions = list(reversed(versions))
        self.combo_version.addItems(versions)
        self.combo_version.setCurrentIndex(versions.index(now))

        # Layouts
        layout_python = QHBoxLayout()
        layout_python.addWidget(self.check_python)
        layout_python.addWidget(self.combo_version)
        layout_python.addStretch()

        layout_r = QHBoxLayout()
        layout_r.addWidget(self.check_r)

        grid = QGridLayout()
        grid.addWidget(self.label_name, 0, 0)
        grid.addWidget(SpacerHorizontal(), 0, 1)
        grid.addWidget(self.text_name, 0, 2)
        grid.addWidget(SpacerVertical(), 1, 0)
        grid.addWidget(self.label_location, 2, 0)
        grid.addWidget(SpacerHorizontal(), 2, 1)
        grid.addWidget(self.label_prefix, 2, 2)
        grid.addWidget(SpacerVertical(), 3, 0)
        grid.addWidget(self.label_packages, 4, 0)
        grid.addLayout(layout_python, 4, 2)
        grid.addWidget(SpacerVertical(), 5, 0)
        grid.addLayout(layout_r, 6, 2)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_ok)

        main_layout = QVBoxLayout()
        main_layout.addLayout(grid)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        main_layout.addLayout(layout_buttons)

        self.setLayout(main_layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.text_name.textChanged.connect(self.refresh)
        self.check_python.stateChanged.connect(self.refresh)
        self.check_r.stateChanged.connect(self.refresh)

        # Setup
        self.text_name.setFocus()
        self.check_python.setChecked(True)
        self.check_r.setChecked(False)
        self.refresh()

    def refresh(self, text=''):
        """Update status of buttons based on data."""
        name = self.name
        self.update_location()

        if self.environments:
            check = self.install_python or self.install_r
            if check and self.is_valid_env_name(name):
                self.button_ok.setDisabled(False)
            else:
                self.button_ok.setDisabled(True)
        self.combo_version.setEnabled(self.install_python)

    @property
    def python_version(self):
        """Return the python version if python was selected for install."""
        version = None
        if self.install_python:
            version = self.combo_version.currentText()
        return version

    @property
    def install_python(self):
        """Return if python was selected for install."""
        return bool(self.check_python.checkState())

    @property
    def install_r(self):
        """Return if r was selected for install."""
        return bool(self.check_r.checkState())
示例#13
0
class PreferencesDialog(DialogBase):
    """Application preferences dialog."""

    sig_urls_updated = Signal(str, str)
    sig_check_ready = Signal()
    sig_reset_ready = Signal()

    def __init__(self, config=CONF, **kwargs):
        """Application preferences dialog."""
        super(PreferencesDialog, self).__init__(**kwargs)

        self.api = AnacondaAPI()
        self.widgets_changed = set()
        self.widgets = []
        self.widgets_dic = {}
        self.config = config

        # Widgets
        self.button_ok = ButtonPrimary('Apply')
        self.button_cancel = ButtonNormal('Cancel')
        self.button_reset = ButtonNormal('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_cancel)
        buttons_layout.addWidget(SpacerHorizontal())
        buttons_layout.addWidget(self.button_ok)

        main_layout = QVBoxLayout()
        main_layout.addLayout(self.grid_layout)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        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):
        """Get configuration option from `main` section."""
        return self.config.get('main', option, None)

    def set_option(self, option, value):
        """Set configuration option in `main` section."""
        self.config.set('main', option, value)

    def get_option_default(self, option):
        """Get configuration option default value in `main` section."""
        return self.config.get_default('main', option)

    def set_option_default(self, option):
        """Set configuration option default value in `main` section."""
        self.set_option(option, self.get_option_default(option))

    def create_widget(self,
                      widget=None,
                      label=None,
                      option=None,
                      hint=None,
                      check=None):
        """Create preference option widget and add to layout."""
        config_value = self.get_option(option)
        widget._text = label
        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)
        h_layout.addWidget(QLabel(hint or ''), 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.widgets_dic[option] = 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, check=None):
        """Create textbox (QLineEdit) preference option."""
        widget = QLineEdit()
        widget.setAttribute(Qt.WA_MacShowFocusRect, False)
        widget.setMinimumWidth(250)

        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, check=None, hint=None):
        """Create checkbox preference option."""
        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))

        api_widget = self.widgets_dic['anaconda_api_url']
        widget.set_warning = lambda w=widget: api_widget
        widget.stateChanged.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 options_changed(self, value=None, widget=None):
        """Callback helper triggered on preference value change."""
        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)))

    def widget_for_option(self, option):
        """Return the widget for the given option."""
        return self.widgets_dic[option]

    # --- API
    # -------------------------------------------------------------------------
    def setup(self):
        """Setup the preferences dialog."""
        # Set values for configuration located elsewhere (anaconda client)
        self.config.set('main', 'anaconda_api_url',
                        self.api.client_get_api_url())
        self.config.set('main', 'ssl_verification', self.api.client_get_ssl())

        def api_url_checker(value):
            """
            Custom checker to use selected ssl option instead of stored one.

            This allows to set an unsafe api url directly on the preferences
            dialog. Without this, one would have to first disable, click
            accept, then open preferences again and change api url for it to
            work.
            """
            ssl_widget = self.widgets_dic.get('ssl_verification')
            verify = ssl_widget.get_value() if ssl_widget else True
            basic_check = self.is_valid_api(value, verify=verify)
            return basic_check

        def ssl_checker(value):
            """Counterpart to api_url_checker."""
            api_url_widget = self.widgets_dic.get('anaconda_api_url')
            api_url = api_url_widget.get_value()
            return self.is_valid_api(api_url, verify=value)

        self.create_textbox(
            'Anaconda API domain',
            'anaconda_api_url',
            check=api_url_checker,
        )
        self.create_checkbox(
            'Enable SSL verification',
            'ssl_verification',
            check=ssl_checker,
            hint=('<i>Disabling this option is not <br>'
                  'recommended for security reasons</i>'),
        )
        self.create_checkbox('Provide analytics', 'provide_analytics')
        self.create_checkbox('Hide quit dialog', 'hide_quit_dialog')
        self.create_checkbox('Hide update dialog on startup',
                             'hide_update_dialog')
        self.create_checkbox('Hide running applications dialog',
                             'hide_running_apps_dialog')
        self.create_checkbox('Enable high DPI scaling',
                             'enable_high_dpi_scaling')

    @staticmethod
    def warn(widget, text=None):
        """Display warning for widget in preferences."""
        label = widget.label_information
        if text:
            pixmap = QPixmap(WARNING_ICON)
            label.setPixmap(
                pixmap.scaled(16, 16, Qt.KeepAspectRatio,
                              Qt.SmoothTransformation))
            label.setToolTip(str(text))
            w = widget.label_information.width() / 2
            h = widget.label_information.height() / 2
            position = widget.label_information.mapToGlobal(QPoint(w, h))
            QCursor.setPos(position)
        else:
            label.setPixmap(QPixmap())
            label.setToolTip('')

    # --- Checkers
    # -------------------------------------------------------------------------
    def is_valid_url(self, url):
        """Check 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 api url.'
        return output, error

    def is_valid_api(self, url, verify=True):
        """Check if a given URL is a valid anaconda api endpoint."""
        output = self.api.download_is_valid_api_url(
            url,
            non_blocking=False,
            verify=verify,
        )
        error = ''
        if not output:
            url_api_1 = ''
            url_api_2 = ''

            if '/api' not in url and self.is_valid_url(url)[0]:
                url_api_1 = url.replace('https://', 'https://api.')
                url_api_1 = url_api_1.replace('http://', 'http://api.')

                if url.endswith('/'):
                    url_api_2 = url + 'api'
                else:
                    url_api_2 = url + '/api'

                error = ('Invalid Anaconda API url. <br>'
                         '<br>Try using:<br><b>{0}</b> or <br>'
                         '<b>{1}</b>'.format(url_api_1, url_api_2))
            else:
                error = ('Invalid Anaconda API url.<br><br>'
                         'Check the url is valid and corresponds to the api '
                         'endpoint.')
        return output, error

    def reset_to_defaults(self):
        """Reset the preferences to the default values."""
        for widget in self.widgets:
            option = widget.option
            default = self.get_option_default(option)
            widget.set_value(default)

            # Special case for anaconda client api
            if option in ['anaconda_api_url', 'ssl_verification']:
                self.set_option(option=option, value=None)

            # Flag all values as updated
            self.options_changed(widget=widget, value=default)
        self.sig_reset_ready.emit()

    def accept(self):
        """Override Qt method."""
        sig_updated = False
        checks = []
        for widget in self.widgets_changed:
            option = widget.option
            value = widget.get_value()
            check, error = widget.check_value(value)
            checks.append(check)

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

            # Settings not stored on Navigator config, but taken from
            # anaconda-client config
            if widget.option == 'anaconda_api_url':
                self.api.client_set_api_url(value)
                sig_updated = True
            if widget.option == 'ssl_verification':
                self.api.client_set_ssl(value)

        # Emit checks ready
        self.sig_check_ready.emit()

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

        if sig_updated:

            def _api_info(worker, output, error):
                conda_url = output['conda_url']
                try:
                    self.sig_urls_updated.emit(anaconda_api_url, conda_url)
                    super(PreferencesDialog, self).accept()
                except RuntimeError:  # Some tests on appveyor/circleci fail
                    pass

            worker = self.api.api_urls()
            worker.sig_chain_finished.connect(_api_info)

        if checks and all(checks):
            super(PreferencesDialog, self).accept()
示例#14
0
class CreateDialog(EnvironmentActionsDialog):
    """Create new environment dialog."""

    MRO = 'mro'
    R = 'r'
    PYTHON_VERSIONS = [u'2.7', u'3.5', u'3.6', u'3.7']
    MRO_MAC_MIN_VERSION = (10, 11)

    def __init__(self, parent=None, api=None):
        """Create new environment dialog."""
        super(CreateDialog, self).__init__(parent=parent, api=api)

        # Widgets
        self.label_name = LabelBase("Name:")
        self.label_location = LabelBase("Location:")
        self.label_prefix = LabelBase('')
        self.text_name = LineEditBase()
        self.label_version = LabelBase("Python version")
        self.label_packages = LabelBase("Packages:")
        self.combo_version = ComboBoxBase()
        self.check_python = CheckBoxBase("Python")
        self.check_r = CheckBoxBase('R')
        self.combo_r_type = ComboBoxBase()
        self.button_ok = ButtonPrimary('Create')
        self.button_cancel = ButtonNormal('Cancel')

        # Widgets setup
        self.align_labels(
            [self.label_name, self.label_location, self.label_packages]
        )
        self.text_name.setPlaceholderText("New environment name")
        self.setMinimumWidth(self.BASE_DIALOG_WIDTH)
        self.setWindowTitle("Create new environment")
        self.text_name.setValidator(self.get_regex_validator())
        self.label_prefix.setObjectName('environment-location')
        self.combo_version.setObjectName('package-version')
        self.combo_r_type.setObjectName('r-type')

        # Supported set of python versions
        self.combo_version.setDisabled(True)

        r_types = [self.R]
        self.combo_r_type.clear()
        self.combo_r_type.addItems(r_types)
        self.combo_r_type.setCurrentIndex(0)
        self.combo_r_type.setEnabled(len(r_types) != 1)

        # Layouts
        layout_packages = QGridLayout()
        layout_packages.addWidget(self.check_python, 0, 0)
        layout_packages.addWidget(SpacerHorizontal(), 0, 1)
        layout_packages.addWidget(self.combo_version, 0, 2)
        layout_packages.addWidget(SpacerVertical(), 1, 0)
        layout_packages.addWidget(self.check_r, 2, 0)
        layout_packages.addWidget(SpacerHorizontal(), 2, 1)
        layout_packages.addWidget(self.combo_r_type, 2, 2)

        grid = QGridLayout()
        grid.addWidget(self.label_name, 0, 0, 1, 1)
        grid.addWidget(SpacerHorizontal(), 0, 1, 1, 1)
        grid.addWidget(self.text_name, 0, 2, 1, 4)
        grid.addWidget(SpacerVertical(), 1, 0, 1, 1)
        grid.addWidget(self.label_location, 2, 0, 1, 1)
        grid.addWidget(SpacerHorizontal(), 2, 1, 1, 1)
        grid.addWidget(self.label_prefix, 2, 2, 1, 4)
        grid.addWidget(SpacerVertical(), 3, 0, 1, 1)
        grid.addWidget(self.label_packages, 4, 0, 1, 1)
        grid.addWidget(SpacerHorizontal(), 4, 1, 1, 1)
        grid.addLayout(layout_packages, 4, 2, 3, 1)

        layout_buttons = QHBoxLayout()
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_cancel)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_ok)

        main_layout = QVBoxLayout()
        main_layout.addLayout(grid)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        main_layout.addLayout(layout_buttons)

        self.setLayout(main_layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.text_name.textChanged.connect(self.refresh)
        self.check_python.stateChanged.connect(self.refresh)
        self.check_r.stateChanged.connect(self.refresh)
        self.sig_setup_ready.connect(self.update_mro)
        self.sig_setup_ready.connect(self.update_pyversions)

        # Setup
        self.text_name.setFocus()
        self.check_python.setChecked(True)
        self.check_r.setChecked(False)
        self.refresh()

    def update_pyversions(self):
        """"""
        python_pks = self._packages.get('python', {})
        python_versions = python_pks.get('versions', [])
        pyvers = set(['.'.join(v.split('.')[:2]) for v in python_versions])

        final_pyvers = []
        for pyver in self.PYTHON_VERSIONS:
            if pyver in pyvers:
                final_pyvers.append(pyver)

        versions = self.PYTHON_VERSIONS
        now = "{}.{}".format(sys.version_info.major, sys.version_info.minor)
        if now not in final_pyvers:
            # Guard against non-standard version, or the coming 3.7
            final_pyvers.append(now)
            final_pyvers.sort()
        final_pyvers = list(reversed(final_pyvers))

        self.combo_version.addItems(final_pyvers)
        self.combo_version.setCurrentIndex(versions.index(now))
        self.combo_version.setEnabled(True)

    def update_mro(self):
        """"""
        # Operating systems with MRO support
        r_types = [self.R]
        if self.api:
            is_mro_available = self.api._conda_api.is_package_available(
                'mro-base',
                channels=self.channels,
            )

            if is_mro_available and BITS_64:
                if ((MAC and MAC_VERSION_INFO > self.MRO_MAC_MIN_VERSION)
                        or WIN or LINUX):
                    r_types = [self.MRO, self.R]

        self.combo_r_type.clear()
        self.combo_r_type.addItems(r_types)
        self.combo_r_type.setCurrentIndex(0)
        self.combo_r_type.setEnabled(len(r_types) != 1)

    def refresh(self, text=''):
        """Update status of buttons based on data."""
        name = self.name
        self.update_location()

        if self.environments:
            check = self.install_python or self.install_r
            if check and self.is_valid_env_name(name):
                self.button_ok.setDisabled(False)
            else:
                self.button_ok.setDisabled(True)
        self.combo_version.setEnabled(self.install_python)
        self.combo_r_type.setEnabled(self.install_r)

    @property
    def python_version(self):
        """Return the python version if python was selected for install."""
        version = None
        if self.install_python:
            version = self.combo_version.currentText()
        return version

    @property
    def install_python(self):
        """Return if python was selected for install."""
        return bool(self.check_python.checkState())

    @property
    def install_r(self):
        """Return if r was selected for install."""
        return bool(self.check_r.checkState())

    @property
    def r_type(self):
        """Return if r was selected for install."""
        return bool(self.combo_r_type.currentText())