class MainWindow(QMainWindow): sig_logged_in = Signal() sig_logged_out = Signal() DOCS_URL = 'https://docs.continuum.io/anaconda/navigator' VIDEOS_URL = "http://content.continuum.io/api/videos" EVENTS_URL = "http://content.continuum.io/api/events" WEBINARS_URL = "http://content.continuum.io/api/webinars" def __init__(self, splash=None): super(MainWindow, self).__init__() self.tracker = None self.splash = splash # Anaconda API self.api = AnacondaAPI() self.busy = False self.logged = False self.username = '' self._login_text = 'Sign in to Anaconda Cloud' self.first_run = CONF.get('main', 'first_run') self.application_update_version = None # Widgets self.frame_header = FrameHeader(self) self.frame_body = FrameBody(self) self.label_logo = LabelHeaderLogo('ANACONDA NAVIGATOR') self.button_logged_text = ButtonLabelLogin('') self.button_logged_username = ButtonLinkLogin('') self.label_update_available = LabelHeaderUpdate('Update available!') self.button_update_available = ButtonHeaderUpdate('Update') self.button_login = ButtonLogin(self._login_text) self.central_widget = QWidget() self.statusbar = self.statusBar() self.progressbar = QProgressBar() self.stack = TabWidgetBody(self) self.home_tab = HomeTab(parent=self) self.environments_tab = EnvironmentsTab(parent=self) self.learning_tab = CommunityTab( parent=self, tags=['webinar', 'documentation', 'video', 'training'], content_urls=[self.VIDEOS_URL, self.WEBINARS_URL]) self.community_tab = CommunityTab(parent=self, tags=['event', 'forum', 'social'], content_urls=[self.EVENTS_URL]) # self.projects_tab = ProjectsTab(parent=self) # Note: Icons are set in CSS self.stack.addTab(self.home_tab, text='Home') self.stack.addTab(self.environments_tab, text='Environments') self.stack.addTab(self.learning_tab, text='Learning') self.stack.addTab(self.community_tab, text='Community') # self.stack.addTab(self.projects_tab, 'Projects') # Widget setup self.button_login.setDefault(True) self.label_logo.setPixmap(QPixmap(images.ANACONDA_NAVIGATOR_LOGO)) self.setWindowTitle("Anaconda Navigator") self.statusbar.addPermanentWidget(self.progressbar) self.progressbar.setVisible(False) # Layout header_layout = QHBoxLayout() header_layout.addWidget(self.label_logo) header_layout.addSpacing(18) header_layout.addWidget(self.label_update_available, 0, Qt.AlignCenter) header_layout.addWidget(self.button_update_available, 0, Qt.AlignCenter) header_layout.addStretch() header_layout.addWidget(self.button_logged_text, 0, Qt.AlignTrailing) header_layout.addWidget(self.button_logged_username, 0, Qt.AlignTrailing) header_layout.addWidget(self.button_login, 0, Qt.AlignTrailing) header_layout.setContentsMargins(0, 0, 0, 0) self.frame_header.setLayout(header_layout) body_layout = QHBoxLayout() body_layout.addWidget(self.stack) body_layout.setContentsMargins(0, 0, 0, 0) self.frame_body.setLayout(body_layout) main_layout = QVBoxLayout() main_layout.addWidget(self.frame_header) main_layout.addWidget(self.frame_body) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) self.central_widget.setLayout(main_layout) self.setContentsMargins(0, 0, 0, 0) self.setCentralWidget(self.central_widget) # Signals self.button_login.clicked.connect(self.login) self.button_logged_username.clicked.connect(self.open_login_page) self.button_update_available.clicked.connect(self.update_application) self.stack.currentChanged.connect(self._track_tab) # This needs to be reworked! # self.projects_tab.sig_apps_updated.connect( # self.home_tab.set_applications) # self.projects_tab.sig_apps_changed.connect( # self.home_tab.set_applications) # self.projects_tab.sig_project_updated.connect( # self.home_tab.set_applications) # self.projects_tab.sig_status_updated.connect(self.update_status_bar) # Setup self.api.set_data_directory(CHANNELS_PATH) self.update_style_sheet() # Helpers # ------------------------------------------------------------------------- def _track_tab(self, index=None): """ Tracks the active tab by index, or set `Home` when no index has been provided. """ if index is None: index = self.stack.currentIndex() text = self.stack.currentText().lower() if self.tracker: page = '/{0}'.format(text) self.tracker.track_page(page) def _metadata_updated(self, worker, path, error): self.set_splash('Updating repodata...') if error: logger.error(str(error)) if path and os.path.isfile(path): with open(path, 'r') as f: data = f.read() try: self._metadata = json.loads(data) except Exception: self._metadata = {} channels = CONF.get('main', 'conda_channels', default=tuple()) if not channels: channels = self.api.conda_get_condarc_channels() CONF.set('main', 'conda_channels', channels) CONF.set('main', 'conda_active_channels', channels) self.api.update_repodata(channels=channels) self.api.sig_repodata_updated.connect(self._repodata_updated) def _repodata_updated(self, paths): self.set_splash('Loading repodata...') self.api.sig_repodata_updated.disconnect(self._repodata_updated) if self.first_run: self.set_splash('Initial configuration...') self.api.create_default_project() worker = self.api.client_load_repodata(paths, self._metadata) worker.sig_finished.connect(self.create_application_projects) # --- Public API # ------------------------------------------------------------------------- def setup(self): """ Perform initial setup and configuration. """ self.set_splash('Updating metadata...') user = self.api.client_set_domain() self.update_login_status(user) self.setup_toolbars() self.set_application_icon() worker = self.api.update_metadata() worker.sig_finished.connect(self._metadata_updated) statusbar = self.statusBar() statusbar.setVisible(False) statusbar.setMaximumHeight(0) statusbar.hide() def create_application_projects(self, worker, output, error): if error: logger.error(str(error)) packages, apps = output self.api.create_application_projects( apps, add_project=self.first_run, ) self.post_setup(apps) self.check_for_updates(packages) def post_setup(self, apps): CONF.set('main', 'first_run', False) self.set_splash('Loading applications...') self.home_tab.setup_tab(apps) # self.set_splash('Loading projects...') # self.projects_tab.setup_tab() self.set_splash('Loading environments...') self.environments_tab.setup_tab(metadata=self._metadata) self.set_splash('Loading content...') self.community_tab.setup_tab() self.set_splash('Loading content...') self.learning_tab.setup_tab() self.update_style_sheet() self.showMaximized() self.post_visible_setup() def set_application_icon(self): """ """ app = QCoreApplication.instance() app_icon = QIcon() app_icon.addFile(images.ANACONDA_ICON_16_PATH, QSize(16, 16)) app_icon.addFile(images.ANACONDA_ICON_24_PATH, QSize(24, 24)) app_icon.addFile(images.ANACONDA_ICON_32_PATH, QSize(32, 32)) app_icon.addFile(images.ANACONDA_ICON_48_PATH, QSize(48, 48)) app_icon.addFile(images.ANACONDA_ICON_256_PATH, QSize(256, 256)) app.setWindowIcon(app_icon) def setup_toolbars(self): menubar = self.menuBar() file_menu = menubar.addMenu('&File') file_menu.addAction( create_action(self, "&Preferences", triggered=self.show_preferences, shortcut="Ctrl+P")) file_menu.addAction( create_action(self, "&Quit", triggered=self.close, shortcut="Ctrl+Q")) helpmenu = menubar.addMenu('&Help') helpmenu.addAction( create_action(self, "&Online Documentation", triggered=lambda: self.open_url(self.DOCS_URL))) helpmenu.addAction( create_action(self, "&Logs viewer", triggered=self.show_log_viewer, shortcut="F6")) helpmenu.addSeparator() helpmenu.addAction( create_action(self, "&About", triggered=self.show_about)) def post_visible_setup(self): if self.splash: self.splash.hide() CONF.set('main', 'first_run', False) # Start the tracker only after post_visible_setup self.tracker = GATracker() self._track_tab(0) # Start tracking home self.fix_tab_ordering() self.show_welcome_screen() def check_for_updates(self, packages=None): # Check if there is an update for navigator! version = self.api.conda_package_version(name='root', pkg='anaconda-navigator') # Temporal mock test # mock_versions = [version, '1.1.0'] # packages['anaconda-navigator'] = {'versions': mock_versions} self.button_update_available.setVisible(False) self.label_update_available.setVisible(False) text = '' if packages: package_data = packages.get('anaconda-navigator') if package_data: versions = package_data.get('versions') if versions and version != versions[-1]: self.application_update_version = versions[-1] self.label_update_available.setText(text) self.label_update_available.setVisible(True) self.button_update_available.setVisible(True) def fix_tab_ordering(self): return for tab in [self.community_tab, self.learning_tab]: self.setTabOrder(self.stack.tabbar.buttons[-1], tab.filter_widgets[0]) for i in range(len(tab.filter_widgets) - 1): self.setTabOrder(tab.filter_widgets[i], tab.filter_widgets[i + 1]) self.setTabOrder(tab.filter_widgets[-1], tab.text_filter) self.setTabOrder(tab.text_filter.button_icon, tab.list) self.setTabOrder(tab.list, self.button_login) # self.button_login.setFocus() self.setTabOrder(self.stack.tabbar.buttons[-1], self.environments_tab.text_search) self.environments_tab.packages_widget.table_last_row.add_focus_widget( self.button_login) self.setTabOrder(self.environments_tab.packages_widget.table_last_row, self.button_login) def update_style_sheet(self): style_sheet = load_style_sheet() # self.home_tab.update_style_sheet(style_sheet) self.environments_tab.update_style_sheet(style_sheet) # self.community_tab.update_style_sheet(style_sheet) # self.learning_tab.update_style_sheet(style_sheet) self.setStyleSheet(style_sheet) def set_splash(self, message): """ Set splash message. """ if self.splash: self.splash.show_message(message) QApplication.processEvents() # --- Login # ------------------------------------------------------------------------- def update_login_status(self, user_data=None): """ Update login button and information. """ if user_data: self.username = user_data.get('login', '') self.logged = True if self.logged: username = self.username anaconda_api_url = CONF.get('main', 'anaconda_api_url') token = self.api.client_load_token(anaconda_api_url) self.button_logged_text.setText('Signed in as') self.button_logged_username.setText(username) url = "{0}/{1}".format(CONF.get('main', 'conda_url'), username) self.button_logged_username.setToolTip(url) self.button_login.setText('Sign out') self.environments_tab.packages_widget.set_token(token) else: self.button_logged_text.setText('') self.button_logged_username.setText('') self.button_login.setText(self._login_text) QApplication.restoreOverrideCursor() def login(self): """ Open up login dialog or log out depending on logged status. """ if self.logged: QApplication.setOverrideCursor(Qt.WaitCursor) self.api.client_logout() self.api.client_remove_token() self.logged = False self.sig_logged_out.emit() self.tracker.track_event('authenticate', 'logout', label=self.username) else: dlg = AuthenticationDialog(self.api, parent=self) if self.tracker: self.tracker.track_page('/login', pagetitle='Login dialog') if dlg.exec_(): self.api.client_store_token(dlg.token) self.username = dlg.username self.logged = True self.sig_logged_in.emit() if self.tracker: self.tracker.track_event('authenticate', 'login', label=self.username) self._track_tab() self.update_login_status() logger.debug(str((self.logged, self.username))) # --- Dialogs # ------------------------------------------------------------------------- def show_preferences(self): """ Display the preferences dialog and apply the needed actions. """ dlg = PreferencesDialog(self) self.tracker.track_page('/preferences', pagetitle='Preferences dialog') set_domains = self.environments_tab.packages_widget.update_domains set_domains = self.environments_tab.packages_widget.update_domains dlg.sig_urls_updated.connect(set_domains) dlg.sig_urls_updated.connect(lambda au, cu: self.login()) dlg.exec_() self._track_tab() def show_about(self): """ Display the `About` dialog with information on the project. """ dlg = AboutDialog(self) self.tracker.track_page('/about', pagetitle='About dialog') dlg.exec_() self._track_tab() def show_log_viewer(self): """ Display the logs viewer to the user """ dlg = LogViewerDialog() self.tracker.track_page('/logs', pagetitle='Log Viewer Dialog') dlg.exec_() self._track_tab() def show_welcome_screen(self): if getattr(self, 'showme', True) and CONF.get('main', 'show_startup', True): from anaconda_navigator.widgets.splash import FirstSplash self.showme = False self.splash.hide() dlg = FirstSplash() dlg.raise_() dlg.exec_() # --- Update Navigator # ------------------------------------------------------------------------- def _update_application(self, worker, output, error): self.button_update_available.setDisabled(False) if error: text = 'Anaconda Navigator Update error:' dlg = MessageBoxError(text=text, error=error, title='Application Update Error') self.tracker.track_page('/update/error', pagetitle='Update Application Error ' 'Message Box') dlg.exec_() else: text = ('Anaconda Navigator Updated succefully.\n\n' 'Please restart the application') dlg = MessageBoxInformation(text=text, title='Application Update') self.tracker.track_page('/update/successful', pagetitle='Application Update Succesful ' 'Message Box') dlg.exec_() self._track_tab() def update_application(self): version = self.application_update_version if version: dlg = DialogUpdateApplication(version=version) self.tracker.track_page('/update', pagetitle='Update Application Dialog') reply = dlg.exec_() if reply: self.tracker.track_event('application', 'updated', version) self.busy = True pkg = 'anaconda-navigator={}'.format(version) worker = self.api.conda_install(name='root', pkgs=[pkg]) worker.sig_finished.connect(self._update_application) self.button_update_available.setDisabled(True) self._track_tab() def update_status_bar(self, message='', timeout=0, val=-1, max_val=-1): """ """ statusbar = self.statusBar() if val != -1 and max_val != -1: self.progressbar.setVisible(True) self.progressbar.setValue(val) self.progressbar.setMaximum(max_val) else: self.progressbar.setVisible(False) if message: statusbar.showMessage(message, timeout) else: statusbar.clearMessage() statusbar.setVisible(False) statusbar.setMaximumHeight(0) statusbar.hide() # --- Url handling # ------------------------------------------------------------------------- def open_url(self, url): qurl = QUrl(url) QDesktopServices.openUrl(qurl) self.tracker.track_event('help', 'documentation', url) def open_login_page(self): """ """ conda_url = CONF.get('main', 'conda_url') url = "{0}/{1}".format(conda_url, self.username) qurl = QUrl(url) QDesktopServices.openUrl(qurl) self.tracker.track_event('content', 'clicked', url) # --- Qt methods # ------------------------------------------------------------------------- def closeEvent(self, event): """ Catch close event. """ # TODO: check if an update is not in progress or things might break!! # if self.busy: show_dialog = not CONF.get('main', 'hide_quit_dialog') if show_dialog: if self.tracker: self.tracker.track_page('/quit', pagetitle='Quit dialog') dlg = QuitApplicationDialog() reply = dlg.exec_() if not reply: event.ignore() self._track_tab() def keyPressEvent(self, event): """ Qt override. """ # if event.key() in [Qt.Key_F5]: # self.update_style_sheet() super(MainWindow, self).keyPressEvent(event) def paintEvent(self, event): """ Qt override. Draw lower left border of the main Stacked Widget. """ super(MainWindow, self).paintEvent(event) tab = self.stack.tabbar tab_pos = self.mapTo(self, tab.pos()) pane_pos = self.mapTo(self, self.stack.pos()) stack_height = self.stack.height() menu_height = self.menuBar().height() header_height = 49 # From css padding = 20 # From css left = 1 # From css extra = 8 # Still wondering where this extra Y delta is deltay = menu_height + header_height + tab.height() + padding + extra x0 = tab_pos.x() + tab.width() + padding - left y0 = tab_pos.y() + deltay y1 = pane_pos.y() + stack_height + deltay - tab.height() - padding painter = QPainter(self) painter.setPen(QPen(QColor('#006f43'), 1, Qt.SolidLine, Qt.RoundCap)) painter.drawLine(x0, y0, x0, y1)
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): """