# -*- coding: utf-8 -*- # pylint: disable=no-name-in-module # ----------------------------------------------------------------------------- # Copyright (c) 2016-2017 Anaconda, Inc. # # May be copied and distributed freely only as part of an Anaconda or # Miniconda installation. # ----------------------------------------------------------------------------- """About Anaconda Navigator dialog.""" from qtpy.QtCore import Qt, Signal from qtpy.QtWidgets import ( QAbstractItemView, QHBoxLayout, QProgressBar, QStackedWidget, QTableWidget, QTableWidgetItem, QTextEdit, QVBoxLayout, ) from anaconda_navigator.api.anaconda_api import AnacondaAPI from anaconda_navigator.widgets import ButtonNormal, ButtonPrimary, LabelBase, SpacerHorizontal, SpacerVertical from anaconda_navigator.widgets.dialogs import DialogBase class PackagesDialog(DialogBase): # pylint: disable=too-many-instance-attributes """Package dependencies dialog.""" sig_setup_ready = Signal() def __init__( # pylint: disable=too-many-arguments,too-many-statements self, parent=None, packages=None, pip_packages=None, remove_only=False, update_only=False, ): """About dialog.""" super().__init__(parent=parent) # Variables self.api = AnacondaAPI() self.actions = None self.packages = packages or [] self.pip_packages = pip_packages or [] # Widgets self.stack = QStackedWidget() self.table = QTableWidget() self.text = QTextEdit() self.label_description = LabelBase() self.label_status = LabelBase() self.progress_bar = QProgressBar() self.button_ok = ButtonPrimary('Apply') self.button_cancel = ButtonNormal('Cancel') # Widget setup self.text.setReadOnly(True) self.stack.addWidget(self.table) self.stack.addWidget(self.text) if remove_only: text = 'The following packages will be removed:
' else: text = 'The following packages will be modified:
' self.label_description.setText(text) self.label_description.setWordWrap(True) self.label_description.setWordWrap(True) self.label_status.setWordWrap(True) self.table.horizontalScrollBar().setVisible(False) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setAlternatingRowColors(True) self.table.setSelectionMode(QAbstractItemView.NoSelection) self.table.setSortingEnabled(True) self._hheader = self.table.horizontalHeader() self._vheader = self.table.verticalHeader() self._hheader.setStretchLastSection(True) self._hheader.setDefaultAlignment(Qt.AlignLeft) self._hheader.setSectionResizeMode(self._hheader.Fixed) self._vheader.setSectionResizeMode(self._vheader.Fixed) self.button_ok.setMinimumWidth(70) self.button_ok.setDefault(True) self.base_minimum_width = 420 if remove_only: self.setWindowTitle('Remove Packages') elif update_only: self.setWindowTitle('Update Packages') else: self.setWindowTitle('Install Packages') self.setMinimumWidth(self.base_minimum_width) # Layouts layout_progress = QHBoxLayout() layout_progress.addWidget(self.label_status) layout_progress.addWidget(SpacerHorizontal()) layout_progress.addWidget(self.progress_bar) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout = QVBoxLayout() layout.addWidget(self.label_description) layout.addWidget(SpacerVertical()) layout.addWidget(self.stack) layout.addWidget(SpacerVertical()) layout.addLayout(layout_progress) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_ok.setDisabled(True) # Setup self.table.setDisabled(True) self.update_status('Solving package specifications', value=0, max_value=0) def setup(self, worker, output, error): # pylint: disable=too-many-branches,too-many-locals,too-many-statements """Setup the widget to include the list of dependencies.""" if not isinstance(output, dict): output = {} packages = sorted(pkg.split('==')[0] for pkg in self.packages) success = output.get('success') error = output.get('error', '') exception_name = output.get('exception_name', '') actions = output.get('actions', []) prefix = worker.prefix if exception_name: message = exception_name else: # All requested packages already installed message = output.get('message', ' ') navi_deps_error = self.api.check_navigator_dependencies(actions, prefix) description = self.label_description.text() if error: description = 'No packages will be modified.' self.stack.setCurrentIndex(1) self.button_ok.setDisabled(True) if self.api.is_offline(): error = str( 'Some of the functionality of Anaconda Navigator will be limited in offline mode.

' 'Installation and upgrade actions will be subject to the packages currently available on your ' 'package cache.' ) self.text.setText(error) elif navi_deps_error: description = 'No packages will be modified.' error = 'Downgrading/removing these packages will modify Anaconda Navigator dependencies.' self.text.setText(error) self.stack.setCurrentIndex(1) message = 'NavigatorDependenciesError' self.button_ok.setDisabled(True) elif success and actions: self.stack.setCurrentIndex(0) # Conda 4.3.x if isinstance(actions, list): actions_link = actions[0].get('LINK', []) actions_unlink = actions[0].get('UNLINK', []) # Conda 4.4.x else: actions_link = actions.get('LINK', []) actions_unlink = actions.get('UNLINK', []) actions_link_names = {p['name'] for p in actions_link} actions_unlink_names = {p['name'] for p in actions_unlink} deps = set() deps = deps.union(actions_link_names) deps = deps.union(actions_unlink_names) deps = deps - set(packages) deps = sorted(list(deps)) modified = actions_unlink_names.intersection(actions_link_names) modified_count = len(modified) plural_total_modified = self.get_plural_suffix(modified_count) removed = actions_unlink_names - actions_link_names removed_count = len(removed) plural_total_removed = self.get_plural_suffix(removed_count) installed = actions_link_names - actions_unlink_names installed_count = len(installed) plural_total_installed = self.get_plural_suffix(installed_count) count_total_packages = len(packages) + len(deps) plural_selected = self.get_plural_suffix(len(packages)) self.table.setRowCount(count_total_packages) self.table.setColumnCount(5) self.table.sortByColumn(4, Qt.AscendingOrder) description = '' if modified: description = f'{description} {modified_count} package{plural_total_modified} will be modified' if removed: description = f'{description} {removed_count} package{plural_total_removed} will be removed' if installed: description = f'{description} {installed_count} package{plural_total_installed} will be installed' if actions_link and actions_unlink or actions_link and not actions_unlink: self.table.showColumn(2) self.table.showColumn(3) self.table.showColumn(4) elif actions_unlink and not actions_link: self.table.hideColumn(2) self.table.hideColumn(3) self.table.hideColumn(4) self.table.setHorizontalHeaderLabels(['Name', 'Unlink', 'Link', 'Channel', 'Action']) for row, pkg in enumerate(packages + deps): link_item = [p for p in actions_link if p['name'] == pkg] if not link_item: link_item = { 'version': '-'.center(len('link')), 'channel': '-'.center(len('channel')), } else: link_item = link_item[0] unlink_item = [p for p in actions_unlink if p['name'] == pkg] if not unlink_item: unlink_item = { 'version': '-'.center(len('link')), } else: unlink_item = unlink_item[0] unlink_version = str(unlink_item['version']) link_version = str(link_item['version']) item_unlink_v = QTableWidgetItem(unlink_version) item_link_v = QTableWidgetItem(link_version) item_link_c = QTableWidgetItem(link_item['channel']) if pkg in packages: item_name = QTableWidgetItem(pkg) else: item_name = QTableWidgetItem('*' + pkg) if pkg in modified: action = 'Modify' elif pkg in removed: action = 'Remove' elif pkg in installed: action = 'Install' else: action = 'Undetermined' action_item = QTableWidgetItem(action) items = [item_name, item_unlink_v, item_link_v, item_link_c, action_item] for column, item in enumerate(items): item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.table.setItem(row, column, item) if deps: message = f'* indicates the package is a dependency of a selected package{plural_selected}
' self.button_ok.setEnabled(True) self.table.resizeColumnsToContents() unlink_width = self.table.columnWidth(1) if unlink_width < 60: self.table.setColumnWidth(1, 60) self.table.setHorizontalHeaderLabels(['Name ', 'Unlink ', 'Link ', 'Channel ', 'Action ']) self.table.setEnabled(True) self.update_status(message=message) self.label_description.setText(description) # Adjust size after data has populated the table self.table.resizeColumnsToContents() width = sum(self.table.columnWidth(i) for i in range(self.table.columnCount())) + 10 delta = (self.width() - self.table.width() + self.table.verticalHeader().width() + 10) new_width = width + delta if new_width < self.base_minimum_width: new_width = self.base_minimum_width self.setMinimumWidth(new_width) self.setMaximumWidth(new_width) self.sig_setup_ready.emit() @staticmethod def get_plural_suffix(count): # pylint: disable=missing-function-docstring return 's' if count != 1 else '' def update_status(self, message='', value=None, max_value=None): """Update status of packages dialog.""" self.label_status.setText(message) if max_value is None and value is None: self.progress_bar.setVisible(False) else: self.progress_bar.setVisible(True) self.progress_bar.setMaximum(max_value) self.progress_bar.setValue(value) # --- Local testing # ----------------------------------------------------------------------------- def local_test(): # pragma: no cover """Run local test.""" from anaconda_navigator.utils.qthelpers import qapplication # pylint: disable=import-outside-toplevel app = qapplication() widget = PackagesDialog(parent=None) widget.show() app.exec_() if __name__ == '__main__': # pragma: no cover local_test()