class DialogUpdateApplication(DialogBase): """Update application dialog.""" WIDTH = 460 def __init__(self, version, config=CONF, startup=False, qa_testing=False): """ Update application dialog. Parameter --------- version: str New version of update available. """ super(DialogUpdateApplication, self).__init__() self.tracker = GATracker() self.label = QLabel( "There's a new version of Anaconda Navigator available. " "We strongly recommend you to update. <br><br>" "If you click yes, Anaconda Navigator will close and then the " "Anaconda Navigator Updater will start.<br><br><br>" "Do you wish to update to <b>Anaconda Navigator {0}</b> now?" "<br><br>".format(version)) self.button_yes = ButtonPrimary('Yes') self.button_no = ButtonNormal('No, remind me later') self.button_no_show = ButtonNormal("No, don't show again") self.config = config if not startup: self.button_no_show.setVisible(False) self.button_no.setText('No') # Widgets setup self.label.setWordWrap(True) self.setMinimumWidth(self.WIDTH) self.setMaximumWidth(self.WIDTH) self.setWindowTitle('Update Application') # On QA testing addicon continuumcrew channel allows to test that # the update checking mechanism is working with a dummy package # version 1000.0.0, this disallows any installation when using that # check if qa_testing: self.button_yes.setDisabled(True) self.button_no.setDisabled(True) self.button_no_show.setDisabled(True) # Layouts layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_no_show) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_no) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_yes) layout = QVBoxLayout() layout.addWidget(self.label) layout_buttons.addWidget(SpacerVertical()) layout_buttons.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_yes.clicked.connect(self.accept) self.button_no.clicked.connect(self.reject) self.button_no_show.clicked.connect(self.no_show) self.button_yes.setFocus() def no_show(self): """Handle not showing updates on startup.""" self.config.set('main', 'hide_update_dialog', True) self.reject()
class DialogChannels(DialogBase): """Dialog to add delete and select active conda package channels.""" sig_channels_updated = Signal(object, object) # added, removed sig_setup_ready = Signal() sig_check_ready = Signal() WIDTH = 550 def __init__(self, parent=None): """Dialog to add delete and select active conda pacakge channels .""" super(DialogChannels, self).__init__(parent) self._parent = parent self._conda_url = 'https://conda.anaconda.org' self.api = AnacondaAPI() self.initial_sources = None self.config_sources = None self.style_sheet = None self._setup_ready = False self._conda_url_setup_ready = False # Widgets self.list = ListWidgetChannels(parent=self, api=self.api) self.label_info = LabelBase( 'Manage channels you want Navigator to include.') self.label_status = LabelBase('Collecting sources...') self.progress_bar = QProgressBar(self) self.button_add = ButtonNormal('Add...') self.button_cancel = ButtonNormal('Cancel') self.button_ok = ButtonPrimary('Update channels') # Widget setup self.frame_title_bar.setVisible(False) self.list.setFrameStyle(QFrame.NoFrame) self.list.setFrameShape(QFrame.NoFrame) self.setWindowFlags(self.windowFlags() | Qt.Popup) self.setWindowOpacity(0.96) self.setMinimumHeight(300) self.setMinimumWidth(self.WIDTH) self.setModal(True) # Layout layout_button = QHBoxLayout() layout_button.addWidget(self.label_info) layout_button.addStretch() layout_button.addWidget(self.button_add) layout_ok = QHBoxLayout() layout_ok.addWidget(self.label_status) layout_ok.addWidget(SpacerHorizontal()) layout_ok.addWidget(self.progress_bar) layout_ok.addWidget(SpacerHorizontal()) layout_ok.addStretch() layout_ok.addWidget(self.button_cancel) layout_ok.addWidget(SpacerHorizontal()) layout_ok.addWidget(self.button_ok) layout = QVBoxLayout() layout.addLayout(layout_button) layout.addWidget(SpacerVertical()) layout.addWidget(self.list) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_ok) self.setLayout(layout) # Signals self.button_add.clicked.connect(self.add_channel) self.button_ok.clicked.connect(self.update_channels) self.button_cancel.clicked.connect(self.reject) self.list.sig_status_updated.connect(self.update_status) self.list.sig_channel_added.connect( lambda v=None: self.set_tab_order()) self.list.sig_channel_added.connect( lambda v=None: self.button_ok.setFocus()) self.list.sig_channel_removed.connect( lambda v=None: self.set_tab_order()) self.list.sig_channel_removed.connect( lambda v=None: self.button_ok.setFocus()) self.list.sig_channel_checked.connect(self.sig_check_ready) self.list.sig_channel_status.connect(self.refresh) self.button_add.setDisabled(True) self.button_ok.setDisabled(True) self.button_cancel.setDisabled(True) self.update_status(action='Collecting sources...', value=0, max_value=0) @staticmethod def _group_sources_and_channels(sources): """ Flatten sources and channels dictionary to list of tuples. [(source, channel), (source, channel)...] """ grouped = [] for source, channels in sources.items(): for channel in channels: grouped.append((source, channel)) return grouped def keyPressEvent(self, event): """Override Qt method.""" key = event.key() if key in [Qt.Key_Escape]: if self.list.is_editing: self.refresh() self.list.is_editing = False else: self.reject() # --- Public API # ------------------------------------------------------------------------- def update_style_sheet(self, style_sheet=None): """Update custom css style sheets.""" if style_sheet is None: self.style_sheet = load_style_sheet() else: self.style_sheet = style_sheet self.setStyleSheet(self.style_sheet) self.setMinimumWidth(SASS_VARIABLES.WIDGET_CHANNEL_DIALOG_WIDTH) try: self.list.update_style_sheet(style_sheet) except Exception: pass def update_api(self, worker, api_info, error): """Update api info.""" self._conda_url = api_info.get('conda_url', 'https://conda.anaconda.org') self._conda_url_setup_ready = True if self._setup_ready: self.sig_setup_ready.emit() def setup(self, worker, conda_config_data, error): """Setup the channels widget.""" self.config_sources = conda_config_data.get('config_sources') self.button_add.setDisabled(False) for source, data in self.config_sources.items(): channels = data.get('channels', []) for channel in channels: item = ListWidgetItemChannel(channel=channel, location=source) item.set_editable(False) self.list.addItem(item) self.set_tab_order() self.button_add.setFocus() self.button_ok.setDefault(True) self.button_cancel.setEnabled(True) self.initial_sources = self.list.sources.copy() self.update_status() self._setup_ready = True if self._conda_url_setup_ready: self.sig_setup_ready.emit() def set_tab_order(self): """Fix the tab ordering in the list.""" if self.list._items: self.setTabOrder(self.button_add, self.list._items[0].button_remove) self.setTabOrder(self.list._items[-1].button_remove, self.button_cancel) self.setTabOrder(self.button_cancel, self.button_ok) self.refresh() def add_channel(self): """Add new conda channel.""" user_rc_path = self.api._conda_api.user_rc_path item = ListWidgetItemChannel(channel='', location=user_rc_path) self.list.addItem(item) self.refresh(False) def update_channels(self): """Update channels list and status.""" sources = self.list.sources original = self._group_sources_and_channels(self.initial_sources) updated = self._group_sources_and_channels(sources) if sorted(original) != sorted(updated): self.sig_channels_updated.emit(*self.sources) self.accept() else: self.reject() def refresh(self, channel_status=True): """Update enable/disable status based on item count.""" self.button_add.setEnabled(channel_status and bool(self.list.count)) self.button_ok.setEnabled(channel_status) self.button_cancel.setEnabled(True) if self.list.count() == 0: self.button_add.setEnabled(True) self.button_ok.setEnabled(False) def update_status(self, action='', message='', value=None, max_value=None): """Update the status and progress bar of the widget.""" visible = bool(action) self.label_status.setText(action) self.label_status.setVisible(visible) if value is not None and max_value is not None: self.progress_bar.setVisible(True) self.progress_bar.setRange(0, max_value) self.progress_bar.setValue(value) else: self.progress_bar.setVisible(False) @property def sources(self): """Return sources to add and remove from config.""" original = self._group_sources_and_channels(self.initial_sources) updated = self._group_sources_and_channels(self.list.sources) original = set(original) updated = set(updated) add = updated - original remove = original - updated return add, remove
class DialogOfflineMode(DialogBase): """Offline mode dialog.""" WIDTH = 460 _MESSAGE_BASE = ( "Some of the functionality of Anaconda Navigator will be limited. " "Conda environment creation will be subject to the packages " "currently available on your package cache." "<br><br>") _MESSAGE_LOC = ( "<b>Offline mode</b> is indicated to the left of the login/logout " "button on the top right corner of the main application window." "<br><br>") _MESSAGE_ENABLE = ( "Offline mode will be disabled automatically when internet " "connectivity is restored." "<br><br>") _MESSAGE_FORCE = ( "You can also manually force <b>Offline mode</b> by enabling " "the setting on the application preferences." "<br>") _MESSAGE_EXTRA_PREF = ( "By checking this option you will force <b>Offline mode</b>." "<br>") MESSAGE_TOOL = _MESSAGE_BASE + _MESSAGE_ENABLE + _MESSAGE_FORCE MESSAGE_PREFERENCES = (_MESSAGE_BASE + _MESSAGE_LOC + _MESSAGE_ENABLE + _MESSAGE_EXTRA_PREF) MESSAGE_DIALOG = (_MESSAGE_BASE + _MESSAGE_LOC + _MESSAGE_ENABLE + _MESSAGE_FORCE) def __init__( self, parent=None, config=CONF, ): """Offline mode dialog.""" super(DialogOfflineMode, self).__init__(parent=parent) self.tracker = GATracker() self.label = QLabel(self.MESSAGE_DIALOG) self.button_ok = ButtonPrimary('Ok') self.checkbox_hide = QCheckBox("Don't show again") self.config = config # Widgets setup self.label.setWordWrap(True) self.setMinimumWidth(self.WIDTH) self.setMaximumWidth(self.WIDTH) self.setWindowTitle('Offline Mode') # Layouts layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.checkbox_hide) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout = QVBoxLayout() layout.addWidget(self.label) layout_buttons.addWidget(SpacerVertical()) layout_buttons.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.handle_accept) # Setup self.button_ok.setFocus() self.setup() def setup(self): """Setup widget content.""" hide_dialog = self.config.get('main', 'hide_offline_dialog') self.checkbox_hide.setChecked(hide_dialog) def handle_accept(self): """Handle not showing updates on startup.""" value = bool(self.checkbox_hide.checkState()) self.config.set('main', 'hide_offline_dialog', value) self.accept()
class LicenseManagerDialog(DialogBase): """License Manager main dialog.""" CONTACT_LINK = 'https://support.continuum.io/' # TODO: Centralize this? # Url, Sender sig_url_clicked = Signal(object, object) def __init__(self, parent=None): """License Manager main dialog.""" super(LicenseManagerDialog, self).__init__(parent=parent) self.api = AnacondaAPI() # Widgets self.message_box = None # For testing self.button_add = ButtonPrimary('Add license') self.button_ok = ButtonNormal('Close') self.button_remove = ButtonNormal('Remove license') self.button_contact = ButtonLink('Please contact us.') self.label_info = LabelBase('Manage your Continuum Analytics ' 'license keys.') self.label_contact = LabelBase('Got a problem with your license? ') self.proxy_model = QSortFilterProxyModel(parent=self) self.model = LicenseModel(parent=self) self.table = LicenseTableView(parent=self) self.delegate = BackgroundDelegate(self.table) # Widget setup self.proxy_model.setSourceModel(self.model) self.table.setItemDelegate(self.delegate) self.table.setModel(self.proxy_model) self.setWindowTitle('License Manager') # Layouts layout_buttons = QHBoxLayout() layout_buttons.addWidget(self.label_info) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addStretch() layout_buttons.addWidget(self.button_add) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_remove) layout_buttons_bottom = QHBoxLayout() layout_buttons_bottom.addWidget(self.label_contact) layout_buttons_bottom.addWidget(self.button_contact) layout_buttons_bottom.addStretch() layout_buttons_bottom.addWidget(self.button_ok) layout = QVBoxLayout() layout.addLayout(layout_buttons) layout.addWidget(SpacerVertical()) layout.addWidget(self.table) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons_bottom) self.setLayout(layout) # Signals self.button_add.clicked.connect(lambda: self.add_license()) self.button_remove.clicked.connect(self.remove_license) self.button_ok.clicked.connect(self.accept) self.button_contact.clicked.connect( lambda v=None: self.sig_url_clicked.emit(self.CONTACT_LINK, 'License Manager')) self.table.sig_dropped.connect(self.handle_drop) # Setup self.button_add.setFocus() self.load_licenses() def handle_drop(self, links): """Handle a drag and drop event.""" self.api.add_license(links) self.load_licenses() def _hide_columns(self): """Hide columns.""" for key, val in COL_MAP.items(): if val in HIDDEN_COLUMNS: self.table.setColumnHidden(key, True) def add_license(self, v=None, path=None): """Add license file.""" if path is None: filename, selected_filter = getopenfilename( self, 'Select license file', filters='License files (*.txt)', basedir=get_home_dir(), ) if filename: paths = [filename] else: paths = [] else: paths = [path] valid_licenses, invalid_licenses = self.api.add_license(paths) for path in invalid_licenses: text = ('File: <b>"{0}"</b>' '<br>is not a valid license file.').format(path) self.message_box = MessageBoxInformation( text=text, title="Invalid license file") self.message_box.exec_() if valid_licenses: self.load_licenses() def remove_license(self, row=None): """Remove license from file.""" if row is None: index = self.table.currentIndex() else: index = self.proxy_model.index(row, 0) model_index = self.proxy_model.mapToSource(index) row_data = self.model.row(model_index.row()) if row_data: text = ('Do you want to remove license for product:<br><br>' '<b>{product}</b> ({issued} - {end_date})') text = text.format(product=row_data.get('product'), end_date=row_data.get('end_date'), issued=row_data.get('issued')) self.message_box = MessageBoxRemove(title='Remove license', text=text) if self.message_box.exec_(): self.api.remove_license(row_data) self.load_licenses() def load_licenses(self): """Load license files.""" res = self.api.load_licenses() self.model.load_licenses(res) self.proxy_model.setSourceModel(self.model) self.table.resizeColumnsToContents() self._hide_columns() self.update_status() def count(self): """Return the number of items in the table.""" return self.table.model().rowCount() def update_status(self): """Update visible and enabled status for widgets based on actions.""" self.button_remove.setEnabled(bool(self.count()))