Exemplo n.º 1
0
Arquivo: apps.py Projeto: bopopescu/QC
class ListItemApplication(ListWidgetItemBase):
    """Item with custom widget for the applications list."""

    ICON_SIZE = 64

    def __init__(
        self,
        name=None,
        display_name=None,
        description=None,
        command=None,
        versions=None,
        image_path=None,
        prefix=None,
        needs_license=False,
        non_conda=False,
    ):
        """Item with custom widget for the applications list."""
        super(ListItemApplication, self).__init__()

        self.api = AnacondaAPI()
        self.prefix = prefix
        self.name = name
        self.display_name = display_name if display_name else name
        self.url = ''
        self.expired = False
        self.needs_license = needs_license
        self.description = description
        self.command = command
        self.versions = versions
        self.image_path = image_path if image_path else ANACONDA_ICON_256_PATH
        self.style_sheet = None
        self.timeout = 2000
        self.non_conda = non_conda
        self._vscode_version_value = None

        # Widgets
        self.button_install = ButtonApplicationInstall("Install")  # or Try!
        self.button_launch = ButtonApplicationLaunch("Launch")
        self.button_options = ButtonApplicationOptions()
        self.label_license = LabelApplicationLicense('')
        self.button_license = ButtonApplicationLicense('')
        self.label_icon = LabelApplicationIcon()
        self.label_name = LabelApplicationName(self.display_name)
        self.label_description = LabelApplicationDescription(self.description)
        self.button_version = ButtonApplicationVersion(
            to_text_string(self.version))
        self.menu_options = QMenu('Application options')
        self.menu_versions = QMenu('Install specific version')
        self.pixmap = QPixmap(self.image_path)
        self.timer = QTimer()
        self.widget = WidgetApplication()
        self.frame_spinner = FrameApplicationSpinner()
        self.spinner = NavigatorSpinner(self.widget, total_width=16)
        lay = QHBoxLayout()
        lay.addWidget(self.spinner)
        self.frame_spinner.setLayout(lay)

        # Widget setup
        self.button_version.setFocusPolicy(Qt.NoFocus)
        self.button_version.setEnabled(True)
        self.label_description.setAlignment(Qt.AlignCenter)
        self.timer.setInterval(self.timeout)
        self.timer.setSingleShot(True)
        self.label_icon.setPixmap(self.pixmap)
        self.label_icon.setScaledContents(True)  # important on High DPI!
        self.label_icon.setMaximumWidth(self.ICON_SIZE)
        self.label_icon.setMaximumHeight(self.ICON_SIZE)
        self.label_icon.setAlignment(Qt.AlignCenter)
        self.label_name.setAlignment(Qt.AlignCenter)
        self.label_name.setWordWrap(True)
        self.label_description.setWordWrap(True)
        self.label_description.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
        self.frame_spinner.setVisible(False)

        # Layouts
        layout_spinner = QHBoxLayout()
        layout_spinner.addWidget(self.button_version, 0, Qt.AlignCenter)
        layout_spinner.addWidget(self.frame_spinner, 0, Qt.AlignCenter)

        layout_license = QHBoxLayout()
        layout_license.addStretch()
        layout_license.addWidget(self.label_license, 0, Qt.AlignCenter)
        layout_license.addWidget(self.button_license, 0, Qt.AlignCenter)
        layout_license.addStretch()

        layout_main = QVBoxLayout()
        layout_main.addWidget(self.button_options, 0, Qt.AlignRight)
        layout_main.addWidget(self.label_icon, 0, Qt.AlignCenter)
        layout_main.addWidget(self.label_name, 0, Qt.AlignCenter)
        layout_main.addLayout(layout_spinner)
        layout_main.addLayout(layout_license)
        layout_main.addWidget(self.label_description, 0, Qt.AlignCenter)
        layout_main.addWidget(self.button_launch, 0, Qt.AlignCenter)
        layout_main.addWidget(self.button_install, 0, Qt.AlignCenter)

        self.widget.setLayout(layout_main)
        self.widget.setStyleSheet(load_style_sheet())
        self.setSizeHint(self.widget_size())
        # This might help with visual quirks on the home screen
        self.widget.setMinimumSize(self.widget_size())

        # Signals
        self.button_install.clicked.connect(self.install_application)
        self.button_launch.clicked.connect(self.launch_application)
        self.button_options.clicked.connect(self.actions_menu_requested)
        self.button_license.clicked.connect(self.launch_url)
        self.timer.timeout.connect(self._application_launched)

        # Setup
        self.update_status()

    # --- Callbacks
    # -------------------------------------------------------------------------
    def _application_launched(self):
        self.button_launch.setDisabled(False)
        update_pointer()

    # --- Helpers
    # -------------------------------------------------------------------------
    def update_style_sheet(self, style_sheet=None):
        """Update custom CSS stylesheet."""
        if style_sheet:
            self.style_sheet = style_sheet
        else:
            self.style_sheet = load_style_sheet()

        self.menu_options.setStyleSheet(self.style_sheet)
        self.menu_versions.setStyleSheet(self.style_sheet)

    def ordered_widgets(self):
        """Return a list of the ordered widgets."""
        return [
            self.button_license, self.button_install, self.button_launch,
            self.button_options
        ]

    @staticmethod
    def widget_size():
        """Return the size defined in the SASS file."""
        return QSize(SASS_VARIABLES.WIDGET_APPLICATION_TOTAL_WIDTH,
                     SASS_VARIABLES.WIDGET_APPLICATION_TOTAL_HEIGHT)

    def launch_url(self):
        """Launch signal for url click."""
        self.widget.sig_url_clicked.emit(self.url)

    def actions_menu_requested(self):
        """Create and display menu for the currently selected application."""
        self.menu_options.clear()
        self.menu_versions.clear()

        # Add versions menu
        versions = self.versions if self.versions else []
        version_actions = []
        for version in reversed(versions):
            action = create_action(self.widget,
                                   version,
                                   triggered=lambda value, version=version:
                                   self.install_application(version=version))

            action.setCheckable(True)
            if self.version == version and self.installed:
                action.setChecked(True)
                action.setDisabled(True)

            version_actions.append(action)

        install_action = create_action(
            self.widget,
            'Install application',
            triggered=lambda: self.install_application())
        install_action.setEnabled(not self.installed)

        update_action = create_action(
            self.widget,
            'Update application',
            triggered=lambda: self.update_application())

        if versions and versions[-1] == self.version:
            update_action.setDisabled(True)
        else:
            update_action.setDisabled(False)

        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            update_action.setDisabled(True)

        remove_action = create_action(
            self.widget,
            'Remove application',
            triggered=lambda: self.remove_application())
        remove_action.setEnabled(self.installed)

        actions = [
            install_action, update_action, remove_action, None,
            self.menu_versions
        ]
        add_actions(self.menu_options, actions)
        add_actions(self.menu_versions, version_actions)
        offset = QPoint(self.button_options.width(), 0)
        position = self.button_options.mapToGlobal(QPoint(0, 0))
        self.menu_versions.setEnabled(len(versions) > 1)
        self.menu_options.move(position + offset)
        self.menu_options.exec_()

    def update_status(self):
        """Update status."""
        # License check
        license_label_text = ''
        license_url_text = ''
        self.url = ''
        self.expired = False
        button_label = 'Install'

        if self.needs_license:
            # TODO: Fix this method to use the api
            license_info = self.api.get_package_license(self.name)
            license_days = self.api.get_days_left(license_info)
            end_date = license_info.get('end_date', '')
            self.expired = license_days == 0
            plural = 's' if license_days != 1 else ''
            is_trial = license_info.get('type', '').lower() == 'trial'

            if self.installed and license_info:
                if is_trial and not self.expired:
                    license_label_text = ('Trial, {days} day{plural} '
                                          'remaining'.format(days=license_days,
                                                             plural=plural))
                    self.url = ''
                elif is_trial and self.expired:
                    license_label_text = 'Trial expired, '
                    license_url_text = 'contact us'
                    self.url = 'mailto:[email protected]'
                elif not is_trial and not self.expired:
                    license_label_text = 'License expires {}'.format(end_date)
                    self.url = ''
                elif not is_trial and self.expired:
                    license_url_text = 'Renew license'
                    self.url = 'mailto:[email protected]'
            elif self.installed and not bool(license_info):
                # Installed but no license found!
                license_url_text = 'No license found'
                self.url = 'mailto:[email protected]'
            else:
                if not self.expired:
                    button_label = 'Install'
                else:
                    button_label = 'Try'

        self.button_license.setText(license_url_text)
        self.button_license.setVisible(bool(self.url))
        self.label_license.setText(license_label_text)
        self.label_license.setVisible(bool(license_label_text))

        # Version and version updates
        if (self.versions and self.version != self.versions[-1]
                and self.installed):
            # The property is used with CSS to display updatable packages.
            self.button_version.setProperty('pressed', True)
            self.button_version.setToolTip('Version {0} available'.format(
                self.versions[-1]))
        else:
            self.button_version.setProperty('pressed', False)

        # For VScode app do not display if new updates are available
        # See: https://github.com/ContinuumIO/navigator/issues/1504
        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            self.button_version.setProperty('pressed', False)
            self.button_version.setToolTip('')

        if not self.needs_license:
            self.button_install.setText(button_label)
            self.button_install.setVisible(not self.installed)
            self.button_launch.setVisible(self.installed)
        else:
            self.button_install.setText('Try' if self.expired else 'Install')
            self.button_launch.setVisible(not self.expired)
            self.button_install.setVisible(self.expired)

        self.button_launch.setEnabled(True)

    def update_versions(self, version=None, versions=None):
        """Update button visibility depending on update availability."""
        logger.debug(str((self.name, self.dev_tool, self.installed)))

        if self.installed and version:
            self.button_options.setVisible(True)
            self.button_version.setText(version)
            self.button_version.setVisible(True)
        elif not self.installed and versions:
            self.button_install.setEnabled(True)
            self.button_version.setText(versions[-1])
            self.button_version.setVisible(True)

        self.versions = versions
        self.version = version
        self.update_status()

    def set_loading(self, value):
        """Set loading status."""
        self.button_install.setDisabled(value)
        self.button_options.setDisabled(value)
        self.button_launch.setDisabled(value)
        self.button_license.setDisabled(value)

        if value:
            self.spinner.start()
        else:
            self.spinner.stop()
            if self.version is None and self.versions is not None:
                version = self.versions[-1]
            else:
                version = self.version
            self.button_version.setText(version)
            self.button_launch.setDisabled(self.expired)

        self.frame_spinner.setVisible(value)
        self.button_version.setVisible(not value)

    # --- Helpers using api
    # -------------------------------------------------------------------------
    def _vscode_version(self):
        """Query the vscode version for the default installation path."""
        version = None
        if self._vscode_version_value is None:
            cmd = [self.api.vscode_executable(), '--version']
            # print(cmd)
            import subprocess
            try:
                output = subprocess.check_output(cmd)
                if PY3:
                    output = output.decode()
                output = [o for o in output.split('\n') if o and '.' in o]
                # print(output)
                if output:
                    version = output[0]
            except Exception:
                pass
                # print(e)

            self._vscode_version_value = version
        else:
            version = self._vscode_version_value

        return version

    @property
    def installed(self):
        """Return the installed status of the package."""
        version = None
        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            # TODO: Vscode program location, check existence
            version = self._vscode_version()
        elif self.prefix:
            version = self.api.conda_package_version(prefix=self.prefix,
                                                     pkg=self.name,
                                                     build=False)
        return bool(version)

    @property
    def version(self):
        """Return the current installed version or the highest version."""
        version = None
        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            version = self._vscode_version()
        elif self.prefix:
            version = self.api.conda_package_version(prefix=self.prefix,
                                                     pkg=self.name,
                                                     build=False)

        if not version:
            version = self.versions[-1]

        return version

    # --- Application actions
    # ------------------------------------------------------------------------
    def install_application(self, value=None, version=None, install=True):
        """
        Update the application on the defined prefix environment.

        This is used for both normal install and specific version install.
        """
        if not version:
            version = self.versions[-1]

        action = C.APPLICATION_INSTALL if install else C.APPLICATION_UPDATE
        self.widget.sig_conda_action_requested.emit(
            action,
            self.name,
            version,
            C.TAB_HOME,
            self.non_conda,
        )
        self.set_loading(True)

    def remove_application(self):
        """Remove the application from the defined prefix environment."""
        self.widget.sig_conda_action_requested.emit(
            C.APPLICATION_REMOVE,
            self.name,
            None,
            C.TAB_HOME,
            self.non_conda,
        )
        self.set_loading(True)

    def update_application(self):
        """Update the application on the defined prefix environment."""
        self.install_application(version=self.versions[-1], install=False)

    def launch_application(self):
        """Launch application installed in prefix environment."""
        leave_path_alone = False
        if self.command is not None:
            if self.non_conda and self.name == GLOBAL_VSCODE_APP:
                leave_path_alone = True
                args = [self.command]
            else:
                args = self.command.split(' ')
                leave_path_alone = True

            self.button_launch.setDisabled(True)
            self.timer.setInterval(self.timeout)
            self.timer.start()
            update_pointer(Qt.BusyCursor)
            self.widget.sig_launch_action_requested.emit(
                self.name,
                args,
                leave_path_alone,
                self.prefix,
                C.TAB_HOME,
                self.non_conda,
            )
Exemplo n.º 2
0
class EnvironmentsTab(WidgetBase):
    """Conda environments tab."""
    BLACKLIST = ['anaconda-navigator', '_license']  # Hide in package manager

    # --- Signals
    # -------------------------------------------------------------------------
    sig_ready = Signal()

    # name, prefix, sender
    sig_item_selected = Signal(object, object, object)

    # sender, func_after_dlg_accept, func_callback_on_finished
    sig_create_requested = Signal()
    sig_clone_requested = Signal()
    sig_import_requested = Signal()
    sig_remove_requested = Signal()

    # button_widget, sender_constant
    sig_channels_requested = Signal(object, object)

    # sender_constant
    sig_update_index_requested = Signal(object)
    sig_cancel_requested = Signal(object)

    # conda_packages_action_dict, pip_packages_action_dict
    sig_packages_action_requested = Signal(object, object)

    def __init__(self, parent=None):
        """Conda environments tab."""
        super(EnvironmentsTab, self).__init__(parent)

        # Variables
        self.api = AnacondaAPI()
        self.current_prefix = None
        self.style_sheet = None

        # Widgets
        self.frame_header_left = FrameTabHeader()
        self.frame_list = FrameEnvironmentsList(self)
        self.frame_widget = FrameEnvironmentsPackages(self)
        self.text_search = LineEditSearch()
        self.list = ListWidgetEnv()
        self.menu_list = QMenu()
        self.button_create = ButtonToolNormal(text="Create")
        self.button_clone = ButtonToolNormal(text="Clone")
        self.button_import = ButtonToolNormal(text="Import")
        self.button_remove = ButtonToolNormal(text="Remove")
        self.button_toggle_collapse = ButtonToggleCollapse()
        self.widget = CondaPackagesWidget(parent=self)

        # Widgets setup
        self.frame_list.is_expanded = True
        self.text_search.setPlaceholderText("Search Environments")
        self.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.button_create.setObjectName("create")  # Needed for QSS selectors
        self.button_clone.setObjectName("clone")
        self.button_import.setObjectName("import")
        self.button_remove.setObjectName("remove")
        self.widget.textbox_search.set_icon_visibility(False)

        # Layouts
        layout_header_left = QVBoxLayout()
        layout_header_left.addWidget(self.text_search)
        self.frame_header_left.setLayout(layout_header_left)

        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.button_create)
        layout_buttons.addWidget(self.button_clone)
        layout_buttons.addWidget(self.button_import)
        layout_buttons.addWidget(self.button_remove)

        layout_list_buttons = QVBoxLayout()
        layout_list_buttons.addWidget(self.frame_header_left)
        layout_list_buttons.addWidget(self.list)
        layout_list_buttons.addLayout(layout_buttons)
        self.frame_list.setLayout(layout_list_buttons)

        layout_widget = QHBoxLayout()
        layout_widget.addWidget(self.widget)
        self.frame_widget.setLayout(layout_widget)

        layout_main = QHBoxLayout()
        layout_main.addWidget(self.frame_list, 10)
        layout_main.addWidget(self.button_toggle_collapse, 1)
        layout_main.addWidget(self.frame_widget, 30)

        self.setLayout(layout_main)

        # Signals for buttons and boxes
        self.button_toggle_collapse.clicked.connect(self.expand_collapse)
        self.button_create.clicked.connect(self.sig_create_requested)
        self.button_clone.clicked.connect(self.sig_clone_requested)
        self.button_import.clicked.connect(self.sig_import_requested)
        self.button_remove.clicked.connect(self.sig_remove_requested)
        self.text_search.textChanged.connect(self.filter_list)

        # Signals for list
        self.list.sig_item_selected.connect(self._item_selected)

        # Signals for packages widget
        self.widget.sig_ready.connect(self.sig_ready)
        self.widget.sig_channels_requested.connect(self.sig_channels_requested)
        self.widget.sig_update_index_requested.connect(
            self.sig_update_index_requested)
        self.widget.sig_cancel_requested.connect(self.sig_cancel_requested)
        self.widget.sig_packages_action_requested.connect(
            self.sig_packages_action_requested)

    # --- Setup methods
    # -------------------------------------------------------------------------
    def setup(self, conda_data):
        """Setup tab content and populates the list of environments."""
        self.set_widgets_enabled(False)
        conda_processed_info = conda_data.get('processed_info')
        environments = conda_processed_info.get('__environments')
        packages = conda_data.get('packages')
        self.current_prefix = conda_processed_info.get('default_prefix')
        self.set_environments(environments)
        self.set_packages(packages)

    def set_environments(self, environments):
        """Populate the list of environments."""
        self.list.clear()
        selected_item_row = 0
        for i, (env_prefix, env_name) in enumerate(environments.items()):
            item = ListItemEnv(prefix=env_prefix, name=env_name)
            item.button_options.clicked.connect(self.show_environment_menu)
            if env_prefix == self.current_prefix:
                selected_item_row = i
            self.list.addItem(item)

        self.list.setCurrentRow(selected_item_row, loading=True)
        self.filter_list()

    def _set_packages(self, worker, output, error):
        """Set packages callback."""
        packages, model_data = output
        self.widget.setup(packages, model_data)
        self.set_widgets_enabled(True)
        self.set_loading(prefix=self.current_prefix, value=False)

    def set_packages(self, packages):
        """Set packages widget content."""
        worker = self.api.process_packages(packages,
                                           prefix=self.current_prefix,
                                           blacklist=self.BLACKLIST)
        worker.sig_chain_finished.connect(self._set_packages)

    def show_environment_menu(self, value=None, position=None):
        """Show the environment actions menu."""
        self.menu_list.clear()
        menu_item = self.menu_list.addAction('Open Terminal')
        menu_item.triggered.connect(
            lambda: self.open_environment_in('terminal'))

        for word in ['Python', 'IPython', 'Jupyter Notebook']:
            menu_item = self.menu_list.addAction("Open with " + word)
            menu_item.triggered.connect(
                lambda x, w=word: self.open_environment_in(w.lower()))

        current_item = self.list.currentItem()
        prefix = current_item.prefix

        if isinstance(position, bool) or position is None:
            width = current_item.button_options.width()
            position = QPoint(width, 0)

        point = QPoint(0, 0)
        parent_position = current_item.button_options.mapToGlobal(point)
        self.menu_list.move(parent_position + position)

        # Disabled actions depending on the environment installed packages
        actions = self.menu_list.actions()
        actions[2].setEnabled(launch.check_prog('ipython', prefix))
        actions[3].setEnabled(launch.check_prog('notebook', prefix))

        self.menu_list.exec_()

    def open_environment_in(self, which):
        """Open selected environment in console terminal."""
        prefix = self.list.currentItem().prefix
        logger.debug("%s, %s", which, prefix)

        if which == 'terminal':
            launch.console(prefix)
        else:
            launch.py_in_console(prefix, which)

    # --- Common Helpers (# FIXME: factor out to common base widget)
    # -------------------------------------------------------------------------
    def _item_selected(self, item):
        """Callback to emit signal as user selects an item from the list."""
        self.set_loading(prefix=item.prefix)
        self.sig_item_selected.emit(item.name, item.prefix, C.TAB_ENVIRONMENT)

    def add_temporal_item(self, name):
        """Creates a temporal item on list while creation becomes effective."""
        item_names = [item.name for item in self.list.items()]
        item_names.append(name)
        index = list(sorted(item_names)).index(name) + 1
        item = ListItemEnv(name=name)
        self.list.insertItem(index, item)
        self.list.setCurrentRow(index)
        self.list.scrollToItem(item)
        item.set_loading(True)

    def expand_collapse(self):
        """Expand or collapse the list selector."""
        if self.frame_list.is_expanded:
            self.frame_list.hide()
            self.frame_list.is_expanded = False
        else:
            self.frame_list.show()
            self.frame_list.is_expanded = True

    def filter_list(self, text=None):
        """Filter items in list by name."""
        text = self.text_search.text().lower()
        for i in range(self.list.count()):
            item = self.list.item(i)
            item.setHidden(text not in item.name.lower())

            if not item.widget.isVisible():
                item.widget.repaint()

    def ordered_widgets(self, next_widget=None):
        """Return a list of the ordered widgets."""
        if next_widget is not None:
            self.widget.table_last_row.add_focus_widget(next_widget)

        ordered_widgets = [
            self.text_search,
        ]
        ordered_widgets += self.list.ordered_widgets()
        ordered_widgets += [
            self.button_create,
            self.button_clone,
            self.button_import,
            self.button_remove,
            self.widget.combobox_filter,
            self.widget.button_channels,
            self.widget.button_update,
            self.widget.textbox_search,
            # self.widget.table_first_row,
            self.widget.table,
            self.widget.table_last_row,
            self.widget.button_apply,
            self.widget.button_clear,
            self.widget.button_cancel,
        ]
        return ordered_widgets

    def refresh(self):
        """Refresh the enabled/disabled status of the widget and subwidgets."""
        is_root = self.current_prefix == self.api.ROOT_PREFIX
        self.button_clone.setDisabled(is_root)
        self.button_remove.setDisabled(is_root)

    def set_loading(self, prefix=None, value=True):
        """Set the item given by `prefix` to loading state."""
        for row, item in enumerate(self.list.items()):
            if item.prefix == prefix:
                item.set_loading(value)
                self.list.setCurrentRow(row)
                break

    def set_widgets_enabled(self, value):
        """Change the enabled status of widgets and subwidgets."""
        self.list.setEnabled(value)
        self.button_create.setEnabled(value)
        self.button_clone.setEnabled(value)
        self.button_import.setEnabled(value)
        self.button_remove.setEnabled(value)
        self.widget.set_widgets_enabled(value)
        if value:
            self.refresh()

    def update_status(self, action='', message='', value=None, max_value=None):
        """Update widget status and progress bar."""
        self.widget.update_status(action=action,
                                  message=message,
                                  value=value,
                                  max_value=max_value)

    def update_style_sheet(self, style_sheet=None):
        """Update custom CSS stylesheet."""
        if style_sheet is None:
            self.style_sheet = load_style_sheet()
        else:
            self.style_sheet = style_sheet

        self.setStyleSheet(self.style_sheet)
        self.list.update_style_sheet(self.style_sheet)
        self.menu_list.setStyleSheet(self.style_sheet)
Exemplo n.º 3
0
class EnvironmentsTab(WidgetBase):
    """
    This tab holds the list of named and application environments in the local
    machine.

    Available options include, `create`, `clone` and `remove` and package
    management.
    """
    BLACKLIST = ['anaconda-navigator']  # Do not show in package manager.

    sig_status_updated = Signal(object, object, object, object)

    def __init__(self, parent=None):
        super(EnvironmentsTab, self).__init__(parent)

        self.api = AnacondaAPI()
        self.last_env_prefix = None
        self.last_env_name = None
        self.previous_environments = None
        self.tracker = GATracker()
        self.metadata = {}

        active_channels = CONF.get('main',  'conda_active_channels', tuple())
        channels = CONF.get('main',  'conda_channels', tuple())
        conda_url = CONF.get('main',  'conda_url',
                             'https:/conda.anaconda.org')
        conda_api_url = CONF.get('main',  'anaconda_api_url',
                                 'https://api.anaconda.org')

        # Widgets
        self.button_clone = ButtonEnvironmentPrimary("Clone")
        self.button_create = ButtonEnvironmentPrimary("Create")
        self.button_remove = ButtonEnvironmentCancel("Remove")
        self.frame_environments = FrameEnvironments(self)
        self.frame_environments_list = FrameEnvironmentsList(self)
        self.frame_environments_list_buttons = FrameEnvironmentsListButtons(self)
        self.frame_environments_packages = FrameEnvironmentsPackages(self)
        self.list_environments = ListWidgetEnvironment()
        self.packages_widget = CondaPackagesWidget(
            self,
            setup=False,
            active_channels=active_channels,
            channels=channels,
            data_directory=CHANNELS_PATH,
            conda_api_url=conda_api_url,
            conda_url=conda_url)
        self.menu_list = QMenu()
        self.text_search = LineEditSearch()
        self.timer_environments = QTimer()

        # Widgets setup
        self.list_environments.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.list_environments.setContextMenuPolicy(Qt.CustomContextMenu)
        self.packages_widget.textbox_search.setAttribute(
            Qt.WA_MacShowFocusRect, False)
        self.packages_widget.textbox_search.set_icon_visibility(False)
        self.text_search.setPlaceholderText("Search Environments")
        self.text_search.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.timer_environments.setInterval(5000)

        # Layouts
        environments_layout = QVBoxLayout()
        environments_layout.addWidget(self.text_search)

        buttons_layout = QHBoxLayout()
        buttons_layout.addWidget(self.button_create)
        buttons_layout.addWidget(self.button_clone)
        buttons_layout.addWidget(self.button_remove)
        buttons_layout.setContentsMargins(0, 0, 0, 0)

        list_buttons_layout = QVBoxLayout()
        list_buttons_layout.addWidget(self.list_environments)
        list_buttons_layout.addLayout(buttons_layout)
        self.frame_environments_list_buttons.setLayout(list_buttons_layout)
        list_buttons_layout.setContentsMargins(0, 0, 0, 0)
        environments_layout.addWidget(self.frame_environments_list_buttons)

        self.frame_environments_list.setLayout(environments_layout)

        packages_layout = QHBoxLayout()
        packages_layout.addWidget(self.packages_widget)
        packages_layout.setContentsMargins(0, 0, 0, 0)
        self.frame_environments_packages.setLayout(packages_layout)

        main_layout = QHBoxLayout()
        main_layout.addWidget(self.frame_environments_list, 1)
        main_layout.addWidget(self.frame_environments_packages, 3)
        main_layout.setContentsMargins(0, 0, 0, 0)
        self.frame_environments.setLayout(main_layout)

        layout = QHBoxLayout()
        layout.addWidget(self.frame_environments)
        self.setLayout(layout)

        # Signals
        self.button_clone.clicked.connect(self.clone_environment)
        self.button_create.clicked.connect(self.create_environment)
        self.button_remove.clicked.connect(self.remove_environment)
        self.list_environments.sig_item_selected.connect(
            self.load_environment)
        self.packages_widget.sig_packages_ready.connect(self.refresh)
        self.packages_widget.sig_channels_updated.connect(self.update_channels)
#        self.packages_widget.sig_environment_cloned.connect(
#            self._environment_created)
#        self.packages_widget.sig_environment_created.connect(
#            self._environment_created)
#        self.packages_widget.sig_environment_removed.connect(
#            self._environment_removed)
        self.text_search.textChanged.connect(self.filter_environments)
        self.timer_environments.timeout.connect(self.refresh_environments)
        self.packages_widget.sig_process_cancelled.connect(
            lambda: self.update_visibility(True))

    # --- Helpers
    # -------------------------------------------------------------------------
    def update_visibility(self, enabled=True):
        self.button_create.setDisabled(not enabled)
        self.button_remove.setDisabled(not enabled)
        self.button_clone.setDisabled(not enabled)
        self.list_environments.setDisabled(not enabled)
        update_pointer()

    def update_style_sheet(self, style_sheet=None):
        if style_sheet is None:
            style_sheet = load_style_sheet()

        self.setStyleSheet(style_sheet)
        self.menu_list.setStyleSheet(style_sheet)
        self.list_environments.setFrameStyle(QFrame.NoFrame)
        self.list_environments.setFrameShape(QFrame.NoFrame)
        self.packages_widget.table.setFrameStyle(QFrame.NoFrame)
        self.packages_widget.table.setFrameShape(QFrame.NoFrame)
        self.packages_widget.layout().setContentsMargins(0, 0, 0, 0)

        size = QSize(16, 16)

        palette = {
            'icon.action.not_installed': QIcon(images.CONDA_MANAGER_NOT_INSTALLED).pixmap(size),
            'icon.action.installed': QIcon(images.CONDA_MANAGER_INSTALLED).pixmap(size),
            'icon.action.remove': QIcon(images.CONDA_MANAGER_REMOVE).pixmap(size),
            'icon.action.add': QIcon(images.CONDA_MANAGER_ADD).pixmap(size),
            'icon.action.upgrade': QIcon(images.CONDA_MANAGER_UPGRADE).pixmap(size),
            'icon.action.downgrade': QIcon(images.CONDA_MANAGER_DOWNGRADE).pixmap(size),
            'icon.upgrade.arrow': QIcon(images.CONDA_MANAGER_UPGRADE_ARROW).pixmap(size),
            'background.remove': QColor(0, 0, 0, 0),
            'background.install': QColor(0, 0, 0, 0),
            'background.upgrade': QColor(0, 0, 0, 0),
            'background.downgrade': QColor(0, 0, 0, 0),
            'foreground.not.installed': QColor("#666"),
            'foreground.upgrade': QColor("#0071a0"),
            }

        self.packages_widget.update_style_sheet(
            style_sheet=style_sheet,
            extra_dialogs={'cancel_dialog': ClosePackageManagerDialog,
                           'apply_actions_dialog': ActionsDialog,
                           'message_box_error': MessageBoxError,
                           },
            palette=palette,
            )

    def get_environments(self):
        """
        Return an ordered dictionary of all existing named environments as
        keys and the prefix as items.

        The dictionary includes the root environment as the first entry.
        """
        environments = OrderedDict()
        environments_prefix = sorted(self.api.conda_get_envs())
        environments['root'] = self.api.ROOT_PREFIX

        for prefix in environments_prefix:
            name = os.path.basename(prefix)
            environments[name] = prefix

        return environments

    def refresh_environments(self):
        """
        Check every `timer_refresh_envs` amount of miliseconds for newly
        created environments and update the list if new ones are found.
        """
        environments = self.get_environments()
        if self.previous_environments is None:
            self.previous_environments = environments.copy()

        if self.previous_environments != environments:
            self.previous_environments = environments.copy()
            self.setup_tab()

    def open_environment_in(self, which):
        environment_prefix = self.list_environments.currentItem().prefix()
        environment_name = self.list_environments.currentItem().text()
        logger.debug("%s, %s", which, environment_prefix)

        if environment_name == 'root':
            environment_prefix = None

        if which == 'terminal':
            launch.console(environment_prefix)
        else:
            launch.py_in_console(environment_prefix, which)

    def set_last_active_prefix(self):
        current_item = self.list_environments.currentItem()
        if current_item:
            self.last_env_prefix = getattr(current_item, '_prefix')
        else:
            self.last_env_prefix = self.api.ROOT_PREFIX
        CONF.set('main', 'last_active_prefix', self.last_env_prefix)

    def setup_tab(self, metadata={}, load_environment=True):
        if metadata:
            self.metadata = metadata

        # show_apps = CONF.get('main', 'show_application_environments')
        envs = self.get_environments()
        self.timer_environments.start()
        self.menu_list.clear()
        menu_item = self.menu_list.addAction('Open Terminal')
        menu_item.triggered.connect(
            lambda: self.open_environment_in('terminal'))

        for word in ['Python', 'IPython', 'Jupyter Notebook']:
            menu_item = self.menu_list.addAction("Open with " + word)
            menu_item.triggered.connect(
                lambda x, w=word: self.open_environment_in(w.lower()))

        def select(value=None, position=None):
            current_item = self.list_environments.currentItem()
            prefix = current_item.prefix()

            if isinstance(position, bool) or position is None:
                width = current_item.button_options.width()
                position = QPoint(width, 0)

#            parent_position = self.list_environments.mapToGlobal(QPoint(0, 0))
            point = QPoint(0, 0)
            parent_position = current_item.button_options.mapToGlobal(point)
            self.menu_list.move(parent_position + position)
            self.menu_list.actions()[2].setEnabled(
                launch.check_prog('ipython', prefix))
            self.menu_list.actions()[3].setEnabled(
                launch.check_prog('notebook', prefix))
            self.menu_list.exec_()

        self.set_last_active_prefix()
        self.list_environments.clear()

#        if show_apps:
#            separator_item = ListItemSeparator('My environments:')
#            self.list_environments.addItem(separator_item)

        for env in envs:
            prefix = envs[env]
            item = ListItemEnvironment(env, prefix=prefix)
            item.button_options.clicked.connect(select)
            self.list_environments.addItem(item)

#        if show_apps:
#            application_envs = self.api.get_application_environments()
#            separator_item = ListItemSeparator('Application environments:')
#            self.list_environments.addItem(separator_item)
#            for app in application_envs:
#                env_prefix = application_envs[app]
#                item = ListItemEnvironment(name=app, prefix=env_prefix)
#                item.button_options.clicked.connect(select)
#                self.list_environments.addItem(item)

        if load_environment:
            self.load_environment()
        else:
            return

        # Adjust Tab Order
        self.setTabOrder(self.text_search,
                         self.list_environments._items[0].widget)
        for i in range(len(self.list_environments._items) - 1):
            self.setTabOrder(self.list_environments._items[i].widget,
                             self.list_environments._items[i+1].widget)
        self.setTabOrder(self.list_environments._items[-1].button_name,
                         self.button_create)
        self.setTabOrder(self.button_create, self.button_clone)
        self.setTabOrder(self.button_clone, self.button_remove)
        self.setTabOrder(self.button_remove,
                         self.packages_widget.combobox_filter)
        self.setTabOrder(self.packages_widget.combobox_filter,
                         self.packages_widget.button_channels)
        self.setTabOrder(self.packages_widget.button_channels,
                         self.packages_widget.button_update)
        self.setTabOrder(self.packages_widget.button_update,
                         self.packages_widget.textbox_search)
        self.setTabOrder(self.packages_widget.textbox_search,
                         self.packages_widget.table_first_row)
        self.setTabOrder(self.packages_widget.table_last_row,
                         self.packages_widget.button_apply)
        self.setTabOrder(self.packages_widget.button_apply,
                         self.packages_widget.button_clear)
        self.setTabOrder(self.packages_widget.button_clear,
                         self.packages_widget.button_cancel)

    def filter_environments(self):
        """
        Filter displayed environments by matching search text.
        """
        text = self.text_search.text().lower()

        for i in range(self.list_environments.count()):
            item = self.list_environments.item(i)
            item.setHidden(text not in item.text().lower())

            if not item.widget.isVisible():
                item.widget.repaint()

    def load_environment(self, item=None):
        self.update_visibility(False)
        if item is None:
            item = self.list_environments.currentItem()

        if item is None or not isinstance(item, ListItemEnvironment):
            prefix = self.api.ROOT_PREFIX
            index = 0
        elif item and isinstance(item, ListItemEnvironment):
            prefix = item.prefix()
        else:
            prefix = self.last_env_prefix if self.last_env_prefix else None

        index = [i for i, it in enumerate(self.list_environments._items)
                 if prefix in it.prefix()]
        index = index[0] if len(index) else 0

        self.list_environments.setCurrentRow(index)
        self.packages_widget.set_environment(prefix=prefix)
        self.packages_widget.setup(check_updates=False,
                                   blacklist=self.BLACKLIST,
                                   metadata=self.metadata)
        self.list_environments.setDisabled(True)
        self.update_visibility(False)
        self.set_last_active_prefix()
#        update_pointer(Qt.BusyCursor)

    def refresh(self):
        self.update_visibility(True)
        self.list_environments.setDisabled(False)
        item = self.list_environments.currentItem()

        try:
            item.set_loading(False)
        except RuntimeError:
            pass
            # C/C++ object not found

        is_root = item.text() == 'root'

        self.button_remove.setDisabled(is_root)
        self.button_clone.setDisabled(is_root)

    def update_channels(self, channels, active_channels):
        """
        Save updated channels to the CONF.
        """
        CONF.set('main', 'conda_active_channels', active_channels)
        CONF.set('main', 'conda_channels', channels)

    # --- Callbacks
    # -------------------------------------------------------------------------
    def _environment_created(self, worker, output, error):
        if error:
            logger.error(str(error))

        self.update_visibility(False)
        for row, environment in enumerate(self.get_environments()):
            if worker.name == environment:
                break

        self.last_env_prefix = self.api.conda_get_prefix_envname(environment)
        self.setup_tab(load_environment=False)
        self.list_environments.setCurrentRow(row)
        self.load_environment()
        self.refresh()
        self.update_visibility(True)
        update_pointer()

    def _environment_removed(self, worker, output, error):
        self.update_visibility(True)
        if error:
            logger.error(str(error))

        self.setup_tab()
        self.list_environments.setCurrentRow(0)

    # --- Public API
    # -------------------------------------------------------------------------
    def update_domains(self, anaconda_api_url, conda_url):
        self.packages_widget.update_domains(
            anaconda_api_url=anaconda_api_url,
            conda_url=conda_url,
            )

    def create_environment(self):
        """
        Create new basic environment with selectable python version.

        Actually makes new env on disc, in directory within the project
        whose name depends on the env name. New project state is saved.
        Should also sync to spec file.
        """
        dlg = CreateEnvironmentDialog(parent=self,
                                      environments=self.get_environments())
        self.tracker.track_page('/environments/create',
                                pagetitle='Create new environment dialog')

        if dlg.exec_():
            name = dlg.text_name.text().strip()
            pyver = dlg.combo_version.currentText()

            if name:
                logger.debug(str('{0}, {1}'.format(name, pyver)))

                self.update_visibility(False)
                update_pointer(Qt.BusyCursor)

                if pyver:
                    pkgs = ['python=' + pyver, 'jupyter']
                else:
                    pkgs = ['jupyter']

                channels = self.packages_widget._active_channels
                logger.debug(str((name, pkgs, channels)))
                self.update_visibility(False)
                worker = self.packages_widget.create_environment(name=name, 
                                                                 packages=pkgs)
#                worker = self.api.conda_create(name=name, pkgs=pkgs,
#                                               channels=channels)
                worker.name = name
                worker.sig_finished.connect(self._environment_created)
        self.tracker.track_page('/environments')

    def remove_environment(self):
        """
        Clone currently selected environment.
        """
        current_item = self.list_environments.currentItem()
        if current_item is not None:
            name = current_item.text()

            if name == 'root':
                return

            dlg = RemoveEnvironmentDialog(environment=name)
            self.tracker.track_page('/environments/remove',
                                    pagetitle='Remove environment dialog')
            if dlg.exec_():
                logger.debug(str(name))
                self.update_visibility(False)
                update_pointer(Qt.BusyCursor)
                worker = self.packages_widget.remove_environment(name=name)
#                worker = self.api.conda_remove(name=name, all_=True)
                worker.sig_finished.connect(self._environment_removed)
#                self.sig_status_updated.emit('Deleting environment '
#                                             '"{0}"'.format(name),
#                                             0, -1, -1)
            self.tracker.track_page('/environments')

    def clone_environment(self):
        """
        Clone currently selected environment.
        """
        current_item = self.list_environments.currentItem()
        if current_item is not None:
            current_name = current_item.text()
            dlg = CloneEnvironmentDialog(parent=self,
                                         environments=self.get_environments())
            self.tracker.track_page('/environments/clone',
                                    pagetitle='Clone environment dialog')

            if dlg.exec_():
                name = dlg.text_name.text().strip()

                if name and current_name:
                    logger.debug(str("{0}, {1}".format(current_name, name)))

                    self.update_visibility(False)
                    update_pointer(Qt.BusyCursor)
                    worker = self.packages_widget.clone_environment(clone=current_name,
                                                                    name=name)
#                    worker = self.api.conda_clone(current_name, name=name)
                    worker.name = name
                    worker.sig_finished.connect(self._environment_created)
            self.tracker.track_page('/environments')

    def import_environment(self):
        """
Exemplo n.º 4
0
class ListItemApplication(QListWidgetItem):
    """
    Item with custom widget for the applications list.
    """
    ICON_SIZE = 48

    def __init__(self,
                 name=None,
                 description=None,
                 command=None,
                 pixmap=None,
                 version=None,
                 versions=None,
                 path=None,
                 dev_tool=True,
                 prefix=None,
                 is_conda_app=False,
                 packages_widget=None):
        super(ListItemApplication, self).__init__()

        self.api = AnacondaAPI()
        self.command = command
        self.dev_tool = dev_tool
        self.installed = False
        self.is_conda_app = is_conda_app
        self.name = name
        self.path = path
        self.pixmap = pixmap if pixmap else QPixmap(ANACONDA_ICON_64_PATH)
        self.prefix = prefix
        self.timeout = 10000  # In miliseconds
        self.version = version
        self.versions = versions
        self.packages_widget = packages_widget

        # Widgets
        self.button_install = ButtonApplicationInstall("Install")
        self.button_launch = ButtonApplicationLaunch("Launch")
        self.button_options = ButtonApplicationOptions()
        self.label_icon = LabelApplicationIcon()
        self.label_name = LabelApplicationName(name)
        self.label_description = LabelApplicationDescription(description)
        #        self.label_update = LabelApplicationUpdate()
        self.button_version = ButtonApplicationVersion(to_text_string(version))
        self.label_spinner = LabelApplicationSpinner()
        #        self.label_version = LabelApplicationVersion(to_text_string(version))
        self.menu_options = QMenu('Application options')
        self.menu_versions = QMenu('Install specific version')
        self.movie_spinner = QMovie(SPINNER_WHITE_16_PATH)
        self.timer = QTimer()
        self.widget = WidgetApplication()

        # Widget setup
        self.button_version.setFocusPolicy(Qt.NoFocus)
        self.label_name.setToolTip(description)
        self.label_description.setAlignment(Qt.AlignCenter)
        self.movie_spinner.start()
        self.timer.setInterval(self.timeout)
        self.timer.setSingleShot(True)
        self.label_icon.setToolTip(description)
        self.label_icon.setPixmap(
            self.pixmap.scaled(self.ICON_SIZE, self.ICON_SIZE,
                               Qt.KeepAspectRatio, Qt.SmoothTransformation))
        self.label_icon.setAlignment(Qt.AlignCenter)
        self.label_name.setAlignment(Qt.AlignCenter)
        self.label_name.setWordWrap(True)
        self.label_description.setWordWrap(True)
        self.label_description.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
        self.label_spinner.setVisible(False)
        self.label_spinner.setMinimumWidth(16)
        self.label_spinner.setMinimumHeight(16)

        # Layouts
        layout = QVBoxLayout()
        layout.addWidget(self.button_options, 0, Qt.AlignRight)
        layout.addWidget(self.label_icon, 0, Qt.AlignCenter)
        layout.addWidget(self.label_name, 0, Qt.AlignCenter)
        layout.addWidget(self.label_description, 0, Qt.AlignCenter)

        #        hlayout = QHBoxLayout()
        #        hlayout.addWidget(self.label_update)
        #        hlayout.addWidget(self.label_version)
        #        layout.addLayout(hlayout)
        #        layout.addWidget(self.label_version, 0, Qt.AlignCenter)
        layout.addWidget(self.button_version, 0, Qt.AlignCenter)
        layout.addWidget(self.label_spinner, 0, Qt.AlignCenter)
        layout.addWidget(self.button_launch, 0, Qt.AlignCenter)
        layout.addWidget(self.button_install, 0, Qt.AlignCenter)

        self.widget.setLayout(layout)
        self.widget.setStyleSheet(load_style_sheet())
        self.setSizeHint(self.widget.sizeHint())

        # Signals
        self.button_install.clicked.connect(self.install_application)
        self.button_launch.clicked.connect(self.launch_application)
        self.button_options.clicked.connect(self.actions_menu_requested)
        self.timer.timeout.connect(self._application_launched)

        # Setup
        self.update_status()

    # --- Callbacks
    # -------------------------------------------------------------------------
    def _application_launched(self):
        """
        """
        self.button_launch.setDisabled(False)
        update_pointer()

    def _application_installed(self, worker, output, error):
        """
        """
        self.handle_action_finished(worker, output, error)

    def _application_updated(self, worker, output, error):
        """
        """
        self.handle_action_finished(worker, output, error)

    def _application_removed(self, worker, output, error):
        """
        """
        self.handle_action_finished(worker, output, error)

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

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

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

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

        logger.debug(message)
        self.widget.sig_status_updated.emit(message)

    def update_style_sheet(self, style_sheet=None):
        if style_sheet is None:
            style_sheet = load_style_sheet()
        self.menu_options.setStyleSheet(style_sheet)
        self.menu_versions.setStyleSheet(style_sheet)

    def actions_menu_requested(self):
        """
        Create and display options menu for the currently selected application.
        """
        self.menu_options.clear()
        self.menu_versions.clear()

        # Add versions menu
        versions = self.versions if self.versions else []
        version_actions = []
        for version in reversed(versions):
            action = create_action(self.widget,
                                   version,
                                   triggered=lambda value, version=version:
                                   self.install_application(version=version))

            action.setCheckable(True)
            if self.version == version:
                action.setChecked(True)
                action.setDisabled(True)

            version_actions.append(action)

        update_action = create_action(
            self.widget,
            'Update application',
            triggered=lambda: self.update_application())

        if versions and versions[-1] == self.version:
            update_action.setDisabled(True)
        else:
            update_action.setDisabled(False)

        remove_action = create_action(
            self.widget,
            'Remove application',
            triggered=lambda: self.remove_application())
        remove_action.setEnabled(self.installed)

        actions = [update_action, remove_action, None, self.menu_versions]
        add_actions(self.menu_options, actions)
        add_actions(self.menu_versions, version_actions)
        offset = QPoint(self.button_options.width(), 0)
        position = self.button_options.mapToGlobal(QPoint(0, 0))
        self.menu_versions.setEnabled(bool(versions))
        self.menu_options.move(position + offset)
        self.menu_options.exec_()

    def update_status(self):
        if self.prefix:
            self.version = self.api.conda_package_version(self.prefix,
                                                          pkg=self.name)
        self.installed = bool(self.version)
        if (self.versions and self.version != self.versions[-1]
                and self.installed):
            self.button_version.setIcon(QIcon(CONDA_MANAGER_UPGRADE_ARROW))
            self.button_version.setStyleSheet(
                "ButtonApplicationVersion {color: #0071a0;}")
            self.button_version.setToolTip('Version {0} available'.format(
                self.versions[-1]))
        else:
            self.button_version.setIcon(QIcon())
            self.button_version.setStyleSheet(
                "ButtonApplicationVersion {color: black;}")

        self.button_install.setVisible(not self.installed)
        self.button_launch.setVisible(self.installed)

    def set_loading(self, value):
        self.button_launch.setDisabled(value)
        self.button_install.setDisabled(value)
        self.button_options.setDisabled(value)

        if value:
            self.label_spinner.setMovie(self.movie_spinner)
        else:
            self.label_spinner.setMovie(None)
            if self.version is None:
                version = self.versions[-1]
            else:
                version = self.version
            self.button_version.setText(version)

        self.label_spinner.setVisible(value)
        self.button_version.setVisible(not value)

    def handle_action_finished(self, worker, output, error):
        if not isinstance(output, dict):
            output = {}
        success = output.get('success', True)

        if error or not success:
            # Error might be harmless if no decoding was possible...
            # Success deserves some sort of messagebox
            logger.error(error)
        self.widget.sig_application_updated.emit(self.name, self.version)

        self.update_status()
        self.set_loading(False)

    def update_versions(self, version=None, versions=None):
        """
        Update button visibility depending on update availability.
        """
        update = versions[-1] != version
        logger.debug(str((self.name, self.dev_tool, self.installed)))

        if self.installed and version:
            self.button_options.setVisible(True)
            self.button_version.setText(version)
            self.button_version.setVisible(True)
        elif not self.installed and versions:
            self.button_install.setEnabled(True)
            self.button_version.setText(versions[-1])
            self.button_version.setVisible(True)

        self.versions = versions
        self.version = version
        self.update_status()

    # --- Public API
    # ------------------------------------------------------------------------
    def install_application(self, value=None, version=None):
        """
        Update the application on the defined prefix environment.

        This is used for both normal install and specific version install.
        """
        if version:
            self.version = version
        else:
            self.version = self.versions[-1]
            version = self.versions[-1]

        pkg = '{0}={1}'.format(self.name, version)
        pkgs = [pkg]
        logger.debug(str((pkg, self.dev_tool)))

        # Check if environment exists and then create or install
        #        is_installed = self.api.conda_package_version(prefix=self.prefix,
        #                                                      pkg=self.name)
        #        pkgs = [pkg] + self.BASIC_PACKAGES
        #        if is_installed:
        #            worker = self.api.conda_install(prefix=self.prefix, pkgs=pkgs)
        #        else:
        #            worker = self.api.conda_create(prefix=self.prefix, pkgs=pkgs)

        worker = self.api.conda_install(prefix=self.prefix, pkgs=pkgs)
        worker.sig_finished.connect(self._application_installed)
        worker.sig_partial.connect(self._partial_output_ready)
        self.set_loading(True)
        self.widget.sig_status_updated.emit('Installing application '
                                            '<b>{0}</b>'.format(self.name))

    def launch_application(self):
        """
        Launch application installed in prefix environment.
        """
        if self.command is not None:
            if self.command.startswith('open'):
                command = self.command.replace("${PREFIX}", self.prefix)
            elif self.prefix:
                command = os.sep.join([self.prefix, 'bin', self.command])
            else:
                command = self.command

            self.button_launch.setDisabled(True)
            self.timer.setInterval(self.timeout)
            self.timer.start()
            update_pointer(Qt.BusyCursor)
            launch(self.path, command)

    def remove_application(self):
        """
        Remove the application from the defined prefix environment.
        """
        pkg = self.name
        pkgs = [pkg]
        logger.debug(str((self.name, self.dev_tool)))
        worker = self.api.conda_remove(prefix=self.prefix, pkgs=pkgs)
        worker.sig_finished.connect(self._application_removed)
        worker.sig_partial.connect(self._partial_output_ready)
        self.set_loading(True)
        self.widget.sig_status_updated.emit('Removing application '
                                            '<b>{0}</b>'.format(self.name))

    def update_application(self):
        """
        Update the application on the defined prefix environment.
        """
        logger.debug(str((self.name, self.dev_tool, self.installed)))
        self.install_application(version=self.versions[-1])
        self.widget.sig_status_updated.emit('Updating application '
                                            '<b>{0}</b>'.format(self.name))