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): """
class PackagesTab(QWidget): """ Packages, environments and commands tab widget. """ sig_status_updated = Signal(object, object, object, object) sig_updated = Signal(object, object) sig_project_commands_updated = Signal(object, object) sig_apps_updated = Signal() def __init__(self): super(PackagesTab, self).__init__() self.api = AnacondaAPI() self.path = None self.project = None # Widgets self.list_environments = EditableList(title='Environments') self.list_commands = EditableList(title='Commands', min_items=0) self.package_manager = CondaPackagesWidget( self, setup=False, data_directory=CHANNELS_PATH, ) # Widget setup self.list_environments.button_add.clicked.disconnect() self.list_environments.button_edit.setVisible(False) # Layout top_layout = QHBoxLayout() top_layout.addWidget(self.list_environments) top_layout.addStretch() top_layout.addWidget(self.list_commands) top_layout.addStretch() main_layout = QVBoxLayout() main_layout.addLayout(top_layout) main_layout.addWidget(self.package_manager) self.setLayout(main_layout) # Signals self.list_commands.sig_item_removed.connect(self.edit_commands) self.list_commands.sig_item_edited.connect(self.add_command) self.list_commands.sig_item_selected.connect(self.select_command) self.list_environments.button_add.clicked.connect(self.add_environment) self.list_environments.sig_item_removed.connect( self.remove_environment) self.list_environments.sig_item_selected.connect( self.choose_environment) self.package_manager.sig_channels_updated.connect(self.set_channels) def load_information(self, path, project=None): """ """ self.path = path if project is None: project = self.api.load_project(path) logger.debug(str((path, project.name))) self.project = project # Load commands commands = project.commands self.list_commands.clear() for command in commands: item = QListWidgetItem(command) self.list_commands.add(item) self.list_commands.setCurrentRow(0) # Load environments environments = project.environments self.list_environments.clear() selected_row = 0 for i, environment in enumerate(environments): item = QListWidgetItem(environment) self.list_environments.add(item) if environment == project.default_environment: selected_row = i self.list_environments.setCurrentRow(selected_row) # Load channels self.package_manager.set_environment(prefix=project.env_prefix(path)) self.package_manager.update_channels(project.default_channels, project.default_channels) def set_channels(self, channels, active_channels): """ """ logger.debug(channels) self.project.default_channels = list(active_channels) self.project.save(self.path) # --- Environments # ------------------------------------------------------------------------- def add_environment(self, text=None, pyver=None): """ 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, envs=self.project.environments) if dlg.exec_(): text = dlg.text_name.text().strip() pyver = dlg.combo_version.currentText() if text: self.setEnabled(False) QApplication.setOverrideCursor(Qt.BusyCursor) # FIXME: This does not seem like a very safe way. What if it fails? self.project.default_environment = text self.project.environments[text] = "{0}.yaml".format(text) prefix = self.project.env_prefix(self.path) if pyver: pkgs = ['python=' + pyver, 'jupyter'] else: pkgs = ['jupyter'] channels = self.project.default_channels logger.debug(str((prefix, pkgs, channels))) worker = self.api.conda_create(prefix=prefix, pkgs=pkgs, channels=channels) worker.text = text worker.sig_finished.connect(self._environment_added) def _environment_added(self, worker, output, error): """ Callback for worker after envrionemnt creation has finished. """ text = worker.text item = QListWidgetItem(text) self.list_environments.add(item) self.list_environments.setCurrentRow( len(self.project.environments) - 1) self.setEnabled(True) self.project.save(self.path) self.choose_environment() QApplication.restoreOverrideCursor() def remove_environment(self, item): """ Delete environment from project and project directory. """ text = item.text() self.project.default_environment = text path = self.project.env_prefix(self.path) logger.debug(str((path, text))) shutil.rmtree(path) self.project.environments.pop(text) self.project.save(self.path) self.choose_environment() def choose_environment(self): """ The user has picked a different environment. Refresh the package list and save the new state of the project. """ current_item_text = self.list_environments.currentItem().text() logger.debug(current_item_text) self.project.default_environment = current_item_text self.project.save(self.path) prefix = self.project.env_prefix(self.path) self.package_manager.set_environment(prefix=prefix) self.package_manager.setup() # --- Commands # ------------------------------------------------------------------------- def add_command(self, command=None): """ Add a new command in the commands list. Adds to the bottom of the list. """ if command: item = QListWidgetItem(command) self.list_commands.add(item) commands = [ self.list_commands.item(i).text() for i in range(self.list_commands.count()) ] self.project.commands = commands self.project.save(self.path) self.sig_project_commands_updated.emit(self.path, self.project.commands) logger.debug(self.project.commands) def edit_commands(self): """ """ commands = [ self.list_commands.item(i).text() for i in range(self.list_commands.count()) ] self.project.commands = commands self.project.save(self.path) self.sig_project_commands_updated.emit(self.path, self.project.commands) logger.debug(self.project.commands) def select_command(self): """ """ self.sig_apps_updated.emit() logger.debug(self.project.commands)