class CloneDialog(EnvironmentActionsDialog): """Clone environment dialog.""" def __init__(self, parent=None, clone_from_name=None): """Clone environment dialog.""" super(CloneDialog, self).__init__(parent=parent) # Widgets self.label_name = LabelBase("Name:") self.text_name = LineEditEnvironment() self.label_location = LabelBase("Location:") self.label_prefix = LabelBase() self.button_ok = ButtonPrimary('Clone') self.button_cancel = ButtonNormal('Cancel') # Widget setup self.align_labels([self.label_name, self.label_location]) self.setMinimumWidth(self.BASE_DIALOG_WIDTH) self.setWindowTitle("Clone from environment: " + clone_from_name) self.text_name.setPlaceholderText("New environment name") self.label_prefix.setObjectName('environment-location') # Layouts grid = QGridLayout() grid.addWidget(self.label_name, 2, 0) grid.addWidget(SpacerHorizontal(), 2, 1) grid.addWidget(self.text_name, 2, 2) grid.addWidget(SpacerVertical(), 3, 0) grid.addWidget(self.label_location, 4, 0) grid.addWidget(self.label_prefix, 4, 2) 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.addLayout(grid) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.text_name.textChanged.connect(self.refresh) self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) # Setup self.text_name.setFocus() self.refresh() def refresh(self, text=''): """Update status of buttons based on combobox selection.""" name = self.name self.update_location() if self.environments: self.button_ok.setDisabled(not self.is_valid_env_name(name))
def __init__(self, *args, **kwargs): super(ProjectEditor, self).__init__(*args, **kwargs) # Widgets self.editor = EditorBase(self) self.button_save = ButtonPrimary('Save') self.button_problems = ButtonProjectProblems('Problems') self.button_suggestions = ButtonProjectSuggestions('Suggestions') self.original_text = None self.problems = None self.suggestions = None # Layouts layout_buttons = QHBoxLayout() layout_buttons.addWidget(self.button_save) layout_buttons.addStretch() layout_buttons.addWidget(self.button_problems) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_suggestions) layout = QVBoxLayout() layout.addLayout(layout_buttons) layout.addWidget(self.editor) self.setLayout(layout) # Signals self.editor.textChanged.connect(self.text_changed) self.button_save.clicked.connect(self.save) self.button_problems.clicked.connect(self.show_problems) self.button_suggestions.clicked.connect(self.show_suggestions)
def __init__(self, parent=None, default=DEFAULT_PROJECTS_PATH): """Select project folder.""" super(ProjectsPathDialog, self).__init__(parent=parent) # Widgets self.label_description = QLabel( "If no path is selected, the default" " one will be used." ) self.label_name = QLabel("Select the projects folder to use") self.label_path = QLabel("Projects path") self.label_info = QLabel('') self.text_path = QLineEdit() self.button_path = ButtonNormal("") self.button_default = ButtonNormal('Use default') self.button_ok = ButtonPrimary('Select') # Widgets setup self.button_path.setObjectName('import') self.default = default self.text_path.setPlaceholderText("projects folder path") self.text_path.setReadOnly(True) self.setMinimumWidth(580) self.setWindowTitle("Select Projects Path") # Layouts layout_grid = QGridLayout() layout_grid.addWidget(self.label_path, 0, 0) layout_grid.addWidget(SpacerHorizontal(), 0, 1) layout_grid.addWidget(self.text_path, 0, 2) layout_grid.addWidget(SpacerHorizontal(), 0, 3) layout_grid.addWidget(self.button_path, 0, 4) layout_grid.addWidget(SpacerVertical(), 1, 0, 1, 4) layout_grid.addWidget(self.label_info, 2, 2, 1, 2) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_default) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout = QVBoxLayout() layout.addWidget(self.label_description) layout.addWidget(SpacerVertical()) layout.addLayout(layout_grid) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_default.clicked.connect(self.use_default) self.button_path.clicked.connect(self.choose) # Setup self.refresh()
def __init__(self, *args, **kwargs): """About dialog.""" super(PasswordDialog, self).__init__(*args, **kwargs) self.wm = WorkerManager() # Widgets self.label_text = LabelBase( 'VSCode will be installed through your system <br> package ' 'manager.<br><br>' 'This action requires elevated privileges. Please <br>provide a ' 'password to forward to sudo') self.lineedit = PasswordEdit() self.label_info = LabelBase() self.button_cancel = ButtonNormal('Cancel') self.button_ok = ButtonPrimary('Ok') self.worker = None self._valid = False self._timer = QTimer() self._timer.setInterval(3000) self._timer.timeout.connect(self.check) # Widgets setup self.button_ok.setMinimumWidth(70) self.button_ok.setDefault(True) self.setWindowTitle("Privilege Elevation Required") self.lineedit.setEchoMode(LineEditBase.Password) # Layouts layout_content = QVBoxLayout() layout_content.addWidget(self.label_text) layout_content.addWidget(SpacerVertical()) layout_content.addWidget(self.lineedit, 0, Qt.AlignBottom) layout_content.addWidget(SpacerVertical()) layout_content.addWidget(self.label_info, 0, Qt.AlignTop) layout_content.addWidget(SpacerVertical()) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout_main = QVBoxLayout() layout_main.addLayout(layout_content) layout_main.addWidget(SpacerVertical()) layout_main.addWidget(SpacerVertical()) layout_main.addLayout(layout_buttons) self.setLayout(layout_main) # Signals self.button_ok.clicked.connect(self.accept2) self.button_cancel.clicked.connect(self.reject2) self.lineedit.textChanged.connect(self.refresh) # Setup self.lineedit.setFocus() self.refresh()
def __init__(self, version, config=CONF, startup=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, you Anaconda Navigator will close and 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') # 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 __init__(self, parent=None, clone_from_name=None): """Clone environment dialog.""" super(CloneDialog, self).__init__(parent=parent) # Widgets self.label_name = LabelBase("Name:") self.text_name = LineEditBase() self.label_location = LabelBase("Location:") self.label_prefix = LabelBase() self.button_ok = ButtonPrimary('Clone') self.button_cancel = ButtonNormal('Cancel') # Widget setup self.align_labels([self.label_name, self.label_location]) self.setMinimumWidth(self.BASE_DIALOG_WIDTH) self.setWindowTitle("Clone from environment: " + clone_from_name) self.text_name.setPlaceholderText("New environment name") self.text_name.setValidator(self.get_regex_validator()) self.label_prefix.setObjectName('environment-location') # Layouts grid = QGridLayout() grid.addWidget(self.label_name, 2, 0) grid.addWidget(SpacerHorizontal(), 2, 1) grid.addWidget(self.text_name, 2, 2) grid.addWidget(SpacerVertical(), 3, 0) grid.addWidget(self.label_location, 4, 0) grid.addWidget(self.label_prefix, 4, 2) 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.addLayout(grid) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.text_name.textChanged.connect(self.refresh) self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) # Setup self.text_name.setFocus() self.refresh()
def __init__(self, config=CONF, **kwargs): """Application preferences dialog.""" super(PreferencesDialog, self).__init__(**kwargs) self.api = AnacondaAPI() self.widgets_changed = set() self.widgets = [] self.widgets_dic = {} self.config = config # Widgets self.button_ok = ButtonPrimary('Apply') self.button_cancel = ButtonNormal('Cancel') self.button_reset = ButtonNormal('Reset to defaults') self.row = 0 # Widget setup self.setWindowTitle("Preferences") # Layouts self.grid_layout = QGridLayout() buttons_layout = QHBoxLayout() buttons_layout.addWidget(self.button_reset) buttons_layout.addStretch() buttons_layout.addWidget(self.button_cancel) buttons_layout.addWidget(SpacerHorizontal()) buttons_layout.addWidget(self.button_ok) main_layout = QVBoxLayout() main_layout.addLayout(self.grid_layout) main_layout.addWidget(SpacerVertical()) main_layout.addWidget(SpacerVertical()) main_layout.addLayout(buttons_layout) self.setLayout(main_layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_reset.clicked.connect(self.reset_to_defaults) self.button_reset.clicked.connect( lambda: self.button_ok.setEnabled(True) ) # Setup self.grid_layout.setSpacing(0) self.setup() self.button_ok.setDisabled(True) self.widgets[0].setFocus() self.button_ok.setDefault(True) self.button_ok.setAutoDefault(True)
def __init__(self, text, packages=(), parent=None): """Accept actions for pacakge manager.""" super(ActionsDialog, self).__init__(parent=parent) self.packages = packages self.label = QLabel(text) self.list = ListWidgetActionPackages(self) self.button_cancel = ButtonDanger('Cancel') self.button_accept = ButtonPrimary('Ok') self.setWindowTitle('Proceed with the following actions?') for item in packages: item = QListWidgetItem(item) self.list.addItem(item) # Layout layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_accept) layout = QVBoxLayout() layout.addWidget(self.label) layout.addWidget(SpacerVertical()) layout.addWidget(self.list) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) self.button_accept.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject)
def __init__(self, parent=None, projects=None): """Create new environment dialog.""" super(CreateDialog, self).__init__(parent=parent) self.projects = projects # Widgets self.label_name = QLabel("Project name") self.text_name = QLineEdit() self.button_ok = ButtonPrimary('Create') self.button_cancel = ButtonNormal('Cancel') # Widgets setup self.text_name.setPlaceholderText("New project name") self.setMinimumWidth(380) self.setWindowTitle("Create new project") self.text_name.setValidator(get_regex_validator()) # Layouts grid = QGridLayout() grid.addWidget(self.label_name, 0, 0) grid.addWidget(SpacerHorizontal(), 0, 1) grid.addWidget(self.text_name, 0, 2) grid.addWidget(SpacerVertical(), 1, 0) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) main_layout = QVBoxLayout() main_layout.addLayout(grid) main_layout.addWidget(SpacerVertical()) main_layout.addWidget(SpacerVertical()) main_layout.addLayout(layout_buttons) self.setLayout(main_layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.text_name.textChanged.connect(self.refresh) # Setup self.refresh()
def __init__(self, parent=None): """Startup splash to display the first time that Navigator runs.""" super(FirstSplash, self).__init__(parent=parent) text = """ Thanks for installing Anaconda! Anaconda Navigator helps you easily start important Python applications and manage the packages in your local Anaconda installation. It also connects you to online resources for learning and engaging with the Python, SciPy, and PyData community. To help us improve Anaconda Navigator, fix bugs, and make it even easier for everyone to use Python, we gather anonymized usage information, just like most web browsers and mobile apps. To opt out of this, please uncheck below (You can always change this setting in the Preferences menu). """ # Variables self.config = CONF # Widgets self.button_ok = ButtonNormal('Ok') self.button_ok_dont_show = ButtonPrimary("Ok, and don't show again") self.checkbox_track = QCheckBox("Yes, I'd like to help improve " "Anaconda.") self.label_about = QLabel(text) self.widget_icon = QSvgWidget(ANACONDA_NAVIGATOR_LOGO) # Widget setup self.frame_title_bar.hide() self.widget_icon.setFixedSize(self.widget_icon.size_for_width(400)) # Layouts layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_ok) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok_dont_show) layout = QVBoxLayout() layout.addWidget(self.widget_icon, 0, Qt.AlignCenter) layout.addWidget(self.label_about) layout.addWidget(self.checkbox_track, 0, Qt.AlignCenter) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_ok.clicked.connect(lambda: self.accept(show_startup=True)) self.button_ok_dont_show.clicked.connect( lambda: self.accept(show_startup=False)) self.setup()
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 __init__(self, *args, **kwargs): """Quit application confirmation dialog.""" self.config = kwargs.pop('config', CONF) super(QuitApplicationDialog, self).__init__(*args, **kwargs) self.widget_icon = QSvgWidget(images.ANACONDA_LOGO) self.label_about = QLabel('Quit Anaconda Navigator?') self.button_ok = ButtonPrimary('Yes') self.button_cancel = ButtonNormal('No') self.buttonbox = QDialogButtonBox(Qt.Horizontal) self.checkbox = CheckBoxBase("Don't show again") # Widgets setup self.setWindowTitle("Quit application") self.widget_icon.setFixedSize(QSize(100, 100)) # Layouts h_layout = QHBoxLayout() h_layout.addWidget(self.widget_icon, 0, Qt.AlignTop) h_layout.addWidget(SpacerHorizontal()) h_layout.addWidget(self.label_about) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) main_layout = QVBoxLayout() main_layout.addLayout(h_layout) main_layout.addWidget(self.checkbox, 0, Qt.AlignRight) main_layout.addWidget(SpacerVertical()) main_layout.addWidget(SpacerVertical()) main_layout.addLayout(layout_buttons) self.setLayout(main_layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) # Setup self.update_style_sheet() self.setup() self.button_cancel.setFocus()
def __init__(self, title='', text='', value=None, value_type=None): """Base message box dialog.""" super(InputDialog, self).__init__() # Widgets self.label = LabelBase(text) self.text = LineEditBase() self.button_ok = ButtonPrimary('Ok') self.button_cancel = ButtonNormal('Cancel') # Widget setup self.setWindowTitle(to_text_string(title)) if value: self.text.setText(str(value)) # Layouts layout = QVBoxLayout() layout_text = QHBoxLayout() layout_text.addWidget(self.label) layout_text.addWidget(SpacerHorizontal()) layout_text.addWidget(self.text) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout.addLayout(layout_text) 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)
def __init__(self, parent=None, problems=None): """Dialog to display anaconda project problems.""" # Check arguments: active channels, must be within channels, otherwise # just remove that channel from active channels super(DialogProblems, self).__init__(parent=parent) self._parent = parent self._problems = problems self.style_sheet = None # Widgets self.list = ListWidgetProblems(parent=self) self.button_ok = ButtonPrimary('Ok') # Widget setup self.frame_title_bar.setVisible(False) self.list.setFrameStyle(QFrame.NoFrame) self.list.setFrameShape(QFrame.NoFrame) self.list.setWordWrap(True) self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint) self.setWindowOpacity(0.96) self.setModal(False) # Layout layout_ok = QHBoxLayout() layout_ok.addStretch() layout_ok.addWidget(self.button_ok) layout = QVBoxLayout() layout.addWidget(self.list) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_ok) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept) self.setup()
def __init__(self, parent=None, projects=None): """Import project from folder or environment files.""" super(ImportDialog, self).__init__(parent=parent) self.projects = projects if projects else {} self.selected_file_filter = None self._path = None # Widgets self.label_info = LabelSpecInfo('', parent=self) self.label_name = QLabel("Project name") self.label_path = QLabel("Specification File") self.text_name = QLineEdit() self.text_path = QLineEdit() self.button_path = ButtonNormal("") self.radio_folder = QRadioButton('From folder') self.radio_spec = QRadioButton('From specification file') self.button_cancel = ButtonNormal('Cancel') self.button_ok = ButtonPrimary('Import') # Widgets setup self.button_path.setObjectName('import') self.button_ok.setDefault(True) self.text_path.setPlaceholderText("File to import from") self.text_name.setPlaceholderText("New project name") self.setMinimumWidth(380) self.setWindowTitle("Import new project") self.text_name.setValidator(get_regex_validator()) # Layouts layout_radio = QHBoxLayout() layout_radio.addWidget(self.radio_folder) layout_radio.addWidget(SpacerHorizontal()) layout_radio.addWidget(self.radio_spec) layout_infile = QHBoxLayout() layout_infile.addWidget(self.text_path) layout_infile.addWidget(SpacerHorizontal()) layout_infile.addWidget(self.button_path) layout_grid = QGridLayout() layout_grid.addWidget(self.label_name, 0, 0, 1, 2) layout_grid.addWidget(SpacerHorizontal(), 0, 2) layout_grid.addWidget(self.text_name, 0, 3) layout_grid.addWidget(SpacerVertical(), 1, 0) layout_grid.addWidget(self.label_path, 2, 0) layout_grid.addWidget(self.label_info, 2, 1) layout_grid.addWidget(SpacerHorizontal(), 2, 2) layout_grid.addLayout(layout_infile, 2, 3) 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.addLayout(layout_radio) layout.addWidget(SpacerVertical()) layout.addLayout(layout_grid) 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_path.clicked.connect(self.choose) self.text_path.textChanged.connect(self.refresh) self.text_name.textChanged.connect(self.refresh) self.radio_folder.toggled.connect(self.refresh) self.radio_spec.toggled.connect(self.refresh) # Setup self.radio_folder.toggle() self.refresh()
def __init__(self, *args, **kwargs): super(ProjectsWidget, self).__init__(*args, **kwargs) self.api = AnacondaAPI() self.timer = None self.timer_content_changed = QTimer() self.project_path = None self.original_content = None self.config = CONF self.timer = None # Widgets self.frame_projects_header = FrameProjectDetailsHeader() self.frame_projects_footer = FrameProjectDetailsFooter() self.button_upload = ButtonPrimary('Upload to Anaconda Cloud') self.button_cancel = ButtonDanger('Cancel') self.label_project_location = LabelProjectLocation( '<b>Project location</b>') self.label_status_message = LabelBase('') self.text_project_location = TextProjectLocation() self.tab_details = QTabWidget() self.file_explorer = ExplorerWidget() self.editor = ProjectEditor(parent=self) # Wigets setup tabbar = self.tab_details.tabBar() tabbar.setFocusPolicy(Qt.StrongFocus) self.tab_details.addTab(self.file_explorer, 'Files') self.tab_details.addTab(self.editor, 'Edit') self.timer_content_changed.setInterval(2000) self.timer_content_changed.timeout.connect(self.check_content_change) self.timer_content_changed.start() # Layouts layout_upload = QHBoxLayout() layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(self.label_status_message) layout_upload.addStretch() layout_upload.addWidget(self.button_cancel) layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(self.button_upload) layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(SpacerHorizontal()) layout_footer = QVBoxLayout() layout_footer.addWidget(SpacerVertical()) layout_footer.addWidget(self.tab_details) layout_footer.addLayout(layout_upload) layout_footer.addWidget(SpacerVertical()) layout_footer.addWidget(SpacerVertical()) self.frame_projects_footer.setLayout(layout_footer) layout = QVBoxLayout() layout.addWidget(self.frame_projects_footer) self.setLayout(layout) # Signals self.editor.sig_dirty_state.connect(self.set_dirty) self.editor.sig_saved.connect(self.save) self.button_upload.clicked.connect(self.upload) self.button_cancel.clicked.connect(self.cancel) self.file_explorer.sig_add_to_project.connect(self.add_to_project) self.button_cancel.setVisible(False) self.file_explorer.set_current_folder(HOME_PATH)
class ProjectEditor(QWidget): sig_dirty_state = Signal(bool) sig_saved = Signal() def __init__(self, *args, **kwargs): super(ProjectEditor, self).__init__(*args, **kwargs) # Widgets self.editor = EditorBase(self) self.button_save = ButtonPrimary('Save') self.button_problems = ButtonProjectProblems('Problems') self.button_suggestions = ButtonProjectSuggestions('Suggestions') self.original_text = None self.problems = None self.suggestions = None # Layouts layout_buttons = QHBoxLayout() layout_buttons.addWidget(self.button_save) layout_buttons.addStretch() layout_buttons.addWidget(self.button_problems) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_suggestions) layout = QVBoxLayout() layout.addLayout(layout_buttons) layout.addWidget(self.editor) self.setLayout(layout) # Signals self.editor.textChanged.connect(self.text_changed) self.button_save.clicked.connect(self.save) self.button_problems.clicked.connect(self.show_problems) self.button_suggestions.clicked.connect(self.show_suggestions) def show_problems(self): """Display problems in a dialog.""" dlg = DialogProblems(parent=self, problems=self.problems) geo_tl = self.button_problems.geometry().topRight() tl = self.button_problems.parentWidget().mapToGlobal(geo_tl) x = tl.x() - dlg.width() y = tl.y() + self.button_problems.height() dlg.move(x, y) dlg.show() def show_suggestions(self): """Display suggestions in a dialog.""" dlg = DialogProblems(parent=self, problems=self.suggestions) geo_tl = self.button_suggestions.geometry().topRight() tl = self.button_suggestions.parentWidget().mapToGlobal(geo_tl) x = tl.x() - dlg.width() y = tl.y() + self.button_suggestions.height() dlg.move(x, y) dlg.show() def save(self): """Save test to editor.""" text = self.editor.toPlainText() self.original_text = text self.button_save.setDisabled(True) self.sig_saved.emit() def text_changed(self): """Callback on text change.""" dirty = self.is_dirty() self.sig_dirty_state.emit(dirty) self.button_save.setEnabled(dirty) def is_dirty(self): """Return if the document is dirty.""" current_text = self.editor.toPlainText() return current_text != self.original_text def text(self): """Return current plain text from editor.""" return self.editor.toPlainText() def set_info(self, problems, suggestions): """Store problems and suggestions for display.""" self.button_problems.setVisible(False) self.button_suggestions.setVisible(False) self.problems = None self.suggetsions = None if problems: self.problems = problems self.button_problems.setVisible(True) if suggestions: self.suggestions = suggestions self.button_suggestions.setVisible(True) def set_text(self, text): """Set editor text.""" self.editor.setPlainText(text) self.original_text = text self.button_save.setDisabled(True) def scroll_value(self): """Get scroll value for vertical bar.""" return self.editor.verticalScrollBar().value() def set_scroll_value(self, value): """Set scroll value for vertical bar.""" return self.editor.verticalScrollBar().setValue(value) def ordered_widgets(self): """Return a list of the ordered widgets.""" ordered_widgets = [ self.button_save, self.button_problems, self.button_suggestions, self.editor ] return ordered_widgets
class MessageBox(DialogBase): """Base message box dialog.""" QUESTION_BOX = 100 INFORMATION_BOX = 101 ERROR_BOX = 102 REMOVE_BOX = 103 sig_url_clicked = Signal(object) def __init__(self, type_, error='', title='', text='', learn_more=None): """Base message box dialog.""" super(MessageBox, self).__init__() from anaconda_navigator.utils.analytics import GATracker self.tracker = GATracker() self.label_text = QLabel(to_text_string(text)) self.textbox_error = QTextEdit() self.button_ok = ButtonPrimary('Ok') self.button_yes = ButtonPrimary('Yes') self.button_no = ButtonNormal('No') self.button_copy = ButtonNormal('Copy text') self.button_learn = ButtonNormal('Learn more') self.button_remove = ButtonDanger('Remove') self.button_cancel = ButtonNormal('Cancel') self.button_send = ButtonNormal('Report Issue', parent=self) self.label_text.setOpenExternalLinks(False) self.label_text.setWordWrap(True) self.label_text.linkActivated.connect(self.url_clicked) self.textbox_error.setReadOnly(True) self.textbox_error.setFrameStyle(QTextEdit.Plain) self.textbox_error.setFrameShape(QTextEdit.NoFrame) self.setMinimumWidth(260) self.textbox_error.verticalScrollBar().show() self.setWindowTitle(to_text_string(title)) error = to_text_string(error).split('\n') error = '<br>'.join(error) self.textbox_error.setText(error) # Layouts layout = QVBoxLayout() layout.addWidget(self.label_text) layout.addWidget(SpacerVertical()) if error: layout.addWidget(self.textbox_error) layout.addWidget(SpacerVertical()) layout.addWidget(self.button_copy) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout.addLayout(layout_buttons) self.layout = layout self.setLayout(layout) # Signals self.button_copy.clicked.connect(self.copy_text) self.button_ok.clicked.connect(self.accept) self.button_yes.clicked.connect(self.accept) self.button_no.clicked.connect(self.reject) self.button_remove.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_send.clicked.connect(self.send) # Setup self.button_learn.setVisible(bool(learn_more)) if bool(learn_more): layout_buttons.addWidget(self.button_learn) layout_buttons.addWidget(SpacerHorizontal()) self.button_learn.clicked.connect( lambda: self.show_url(learn_more) ) if type_ == self.ERROR_BOX: layout_buttons.addWidget(self.button_send) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.INFORMATION_BOX: layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.textbox_error.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.QUESTION_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_no) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_yes) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.REMOVE_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_remove) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_send.setVisible(False) self.layout_buttons = layout_buttons def url_clicked(self, url): """Emit url interaction.""" self.sig_url_clicked.emit(url) def copy_text(self): """Copy all the content of the displayed error message.""" self.textbox_error.selectAll() self.textbox_error.copy() def show_url(self, url=None): """Open url in default browser.""" if url: qurl = QUrl(url) QDesktopServices.openUrl(qurl) self.tracker.track_event('help', 'documentation', url) def send(self): """Send error report to github and create an issue with a template.""" import webbrowser from anaconda_navigator.utils.analytics import GATracker base = "https://github.com/ContinuumIO/anaconda-issues/issues/new?{0}" template = ''' ## Main error {text} ## Traceback ``` {trace} ``` ## System information ``` {info} ``` ''' info = GATracker().info info = '\n'.join('{}: {}'.format(k, v) for k, v in info.items()) query = parse.urlencode( { 'title': "Navigator Error", 'labels': "tag:navigator", 'body': template.format( text=self.text, trace=self.error, info=info ) } ) url = base.format(query) webbrowser.open_new_tab(url)
class ProjectsWidget(WidgetBase): """Main projects widget.""" sig_saved = Signal() sig_login_requested = Signal() def __init__(self, *args, **kwargs): super(ProjectsWidget, self).__init__(*args, **kwargs) self.api = AnacondaAPI() self.timer = None self.timer_content_changed = QTimer() self.project_path = None self.original_content = None self.config = CONF self.timer = None # Widgets self.frame_projects_header = FrameProjectDetailsHeader() self.frame_projects_footer = FrameProjectDetailsFooter() self.button_upload = ButtonPrimary('Upload to Anaconda Cloud') self.button_cancel = ButtonDanger('Cancel') self.label_project_location = LabelProjectLocation( '<b>Project location</b>') self.label_status_message = LabelBase('') self.text_project_location = TextProjectLocation() self.tab_details = QTabWidget() self.file_explorer = ExplorerWidget() self.editor = ProjectEditor(parent=self) # Wigets setup tabbar = self.tab_details.tabBar() tabbar.setFocusPolicy(Qt.StrongFocus) self.tab_details.addTab(self.file_explorer, 'Files') self.tab_details.addTab(self.editor, 'Edit') self.timer_content_changed.setInterval(2000) self.timer_content_changed.timeout.connect(self.check_content_change) self.timer_content_changed.start() # Layouts layout_upload = QHBoxLayout() layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(self.label_status_message) layout_upload.addStretch() layout_upload.addWidget(self.button_cancel) layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(self.button_upload) layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(SpacerHorizontal()) layout_footer = QVBoxLayout() layout_footer.addWidget(SpacerVertical()) layout_footer.addWidget(self.tab_details) layout_footer.addLayout(layout_upload) layout_footer.addWidget(SpacerVertical()) layout_footer.addWidget(SpacerVertical()) self.frame_projects_footer.setLayout(layout_footer) layout = QVBoxLayout() layout.addWidget(self.frame_projects_footer) self.setLayout(layout) # Signals self.editor.sig_dirty_state.connect(self.set_dirty) self.editor.sig_saved.connect(self.save) self.button_upload.clicked.connect(self.upload) self.button_cancel.clicked.connect(self.cancel) self.file_explorer.sig_add_to_project.connect(self.add_to_project) self.button_cancel.setVisible(False) self.file_explorer.set_current_folder(HOME_PATH) def add_to_project(self, fname): """Add selected file to project.""" file_path = os.path.join( self.project_path, os.path.basename(fname), ) try: shutil.copyfile(fname, file_path) except Exception: pass def check_content_change(self): """Check if content of anaconda-project changed outside.""" if self.project_path: project_config_path = os.path.join(self.project_path, 'anaconda-project.yml') if os.path.isfile(project_config_path): current_content = self.editor.text() with open(project_config_path, 'r') as f: data = f.read() if (current_content != data and data != self.original_content): self.load_project(self.project_path) def set_dirty(self, state): """Set dirty state editor tab.""" text = 'Edit*' if state else 'Edit' self.tab_details.setTabText(1, text) def before_delete(self): """Before deleting a folder, ensure it is not the same as the cwd.""" self.file_explorer.set_current_folder(HOME_PATH) def clear(self): """Reset view for proect details.""" self.text_project_location.setText('') self.editor.set_text('') def cancel(self): """Cancel ongoing project process.""" # TODO: when running project. Cancel ongoing process! self.button_cancel.setVisible(False) self.button_upload.setEnabled(True) def _upload(self, worker, output, error): """Upload callback.""" error = error if error else '' errors = [] if output is not None: errors = output.errors # print(output.status_description) # print(output.logs) # print(errors) if error or errors: if errors: error_msg = error or '\n'.join(errors) elif error: error_msg = 'Upload failed!' self.update_status(error_msg) else: self.update_status('Project <b>{0}</b> upload successful'.format( worker.name)) self.timer = QTimer() self.timer.setSingleShot(True) self.timer.setInterval(10000) self.timer.timeout.connect(lambda: self.update_status('')) self.timer.start() self.button_upload.setEnabled(True) self.button_cancel.setVisible(False) def update_status(self, message): """Update Status Bar message.""" self.label_status_message.setText(message) def upload(self): """Upload project to Anaconda Cloud.""" # Check if is logged in? if not self.api.is_logged_in(): self.update_status('You need to log in to Anaconda Cloud') self.sig_login_requested.emit() self.update_status('') return project = self.api.project_load(self.project_path) name = project.name or os.path.basename(self.project_path) # Check if saved? if self.editor.is_dirty(): self.update_status('Saving project <b>{0}</b>'.format( project.name)) self.editor.save() project = self.api.project_load(self.project_path) if not project.problems: username, token = self.api.get_username_token() self.button_cancel.setVisible(True) worker = self.api.project_upload( project, username=username, token=token, ) worker.sig_finished.connect(self._upload) worker.name = project.name self.button_upload.setEnabled(False) msg = 'Uploading project <b>{0}</b> to Anaconda Cloud.'.format( project.name) self.update_status(msg) else: self.update_status( 'Problems must be fixed before uploading <b>{0}</b>' ''.format(name)) def save(self): """Save current edited project.""" project_config_path = os.path.join(self.project_path, 'anaconda-project.yml') data = self.editor.text() if os.path.isfile(project_config_path): with open(project_config_path, 'w') as f: data = f.write(data) self.load_project(self.project_path, overwrite=False) self.sig_saved.emit() def load_project(self, project_path, overwrite=True): """Load a conda project located at path.""" self.project_path = project_path project = self.api.project_load(project_path) self.project = project self.text_project_location.setText(project_path) self.file_explorer.set_current_folder(project_path) project_config_path = os.path.join(project_path, 'anaconda-project.yml') data = '' if os.path.isfile(project_config_path): with open(project_config_path, 'r') as f: data = f.read() self.original_content = data if overwrite: self.editor.set_text(data) self.set_dirty(False) self.file_explorer.set_home(project_path) self.update_error_status(project) self.update_status('') def ordered_widgets(self): """Return a list of the ordered widgets.""" tabbar = self.tab_details.tabBar() ordered_widgets = [tabbar] ordered_widgets += self.file_explorer.ordered_widgets() ordered_widgets += self.editor.ordered_widgets() ordered_widgets += [self.button_upload] return ordered_widgets def update_error_status(self, project): """Update problems and suggestions.""" if project: problems = project.problems suggestions = project.suggestions if problems or suggestions: icon = QIcon(WARNING_ICON) self.tab_details.setTabIcon(1, icon) else: self.tab_details.setTabIcon(1, QIcon()) self.editor.set_info(problems, suggestions)
def __init__(self, type_, error='', title='', text='', learn_more=None): """Base message box dialog.""" super(MessageBox, self).__init__() from anaconda_navigator.utils.analytics import GATracker self.tracker = GATracker() self.label_text = QLabel(to_text_string(text)) self.textbox_error = QTextEdit() self.button_ok = ButtonPrimary('Ok') self.button_yes = ButtonPrimary('Yes') self.button_no = ButtonNormal('No') self.button_copy = ButtonNormal('Copy text') self.button_learn = ButtonNormal('Learn more') self.button_remove = ButtonDanger('Remove') self.button_cancel = ButtonNormal('Cancel') self.button_send = ButtonNormal('Report Issue', parent=self) self.label_text.setOpenExternalLinks(False) self.label_text.setWordWrap(True) self.label_text.linkActivated.connect(self.url_clicked) self.textbox_error.setReadOnly(True) self.textbox_error.setFrameStyle(QTextEdit.Plain) self.textbox_error.setFrameShape(QTextEdit.NoFrame) self.setMinimumWidth(260) self.textbox_error.verticalScrollBar().show() self.setWindowTitle(to_text_string(title)) error = to_text_string(error).split('\n') error = '<br>'.join(error) self.textbox_error.setText(error) # Layouts layout = QVBoxLayout() layout.addWidget(self.label_text) layout.addWidget(SpacerVertical()) if error: layout.addWidget(self.textbox_error) layout.addWidget(SpacerVertical()) layout.addWidget(self.button_copy) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout.addLayout(layout_buttons) self.layout = layout self.setLayout(layout) # Signals self.button_copy.clicked.connect(self.copy_text) self.button_ok.clicked.connect(self.accept) self.button_yes.clicked.connect(self.accept) self.button_no.clicked.connect(self.reject) self.button_remove.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_send.clicked.connect(self.send) # Setup self.button_learn.setVisible(bool(learn_more)) if bool(learn_more): layout_buttons.addWidget(self.button_learn) layout_buttons.addWidget(SpacerHorizontal()) self.button_learn.clicked.connect( lambda: self.show_url(learn_more) ) if type_ == self.ERROR_BOX: layout_buttons.addWidget(self.button_send) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.INFORMATION_BOX: layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.textbox_error.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.QUESTION_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_no) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_yes) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.REMOVE_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_remove) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_send.setVisible(False) self.layout_buttons = layout_buttons
class PreferencesDialog(DialogBase): """Application preferences dialog.""" sig_urls_updated = Signal(str, str) sig_check_ready = Signal() sig_reset_ready = Signal() def __init__(self, config=CONF, **kwargs): """Application preferences dialog.""" super(PreferencesDialog, self).__init__(**kwargs) self.api = AnacondaAPI() self.widgets_changed = set() self.widgets = [] self.widgets_dic = {} self.config = config # Widgets self.button_ok = ButtonPrimary('Apply') self.button_cancel = ButtonNormal('Cancel') self.button_reset = ButtonNormal('Reset to defaults') self.row = 0 # Widget setup self.setWindowTitle("Preferences") # Layouts self.grid_layout = QGridLayout() buttons_layout = QHBoxLayout() buttons_layout.addWidget(self.button_reset) buttons_layout.addStretch() buttons_layout.addWidget(self.button_cancel) buttons_layout.addWidget(SpacerHorizontal()) buttons_layout.addWidget(self.button_ok) main_layout = QVBoxLayout() main_layout.addLayout(self.grid_layout) main_layout.addWidget(SpacerVertical()) main_layout.addWidget(SpacerVertical()) main_layout.addLayout(buttons_layout) self.setLayout(main_layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_reset.clicked.connect(self.reset_to_defaults) self.button_reset.clicked.connect( lambda: self.button_ok.setEnabled(True) ) # Setup self.grid_layout.setSpacing(0) self.setup() self.button_ok.setDisabled(True) self.widgets[0].setFocus() self.button_ok.setDefault(True) self.button_ok.setAutoDefault(True) # --- Helpers # ------------------------------------------------------------------------- def get_option(self, option): """Get configuration option from `main` section.""" return self.config.get('main', option, None) def set_option(self, option, value): """Set configuration option in `main` section.""" self.config.set('main', option, value) def get_option_default(self, option): """Get configuration option default value in `main` section.""" return self.config.get_default('main', option) def set_option_default(self, option): """Set configuration option default value in `main` section.""" self.set_option(option, self.get_option_default(option)) def create_widget( self, widget=None, label=None, option=None, hint=None, check=None, info=None, ): """Create preference option widget and add to layout.""" config_value = self.get_option(option) widget._text = label widget.label = QLabel(label) widget.option = option widget.set_value(config_value) widget.label_information = QLabel() widget.label_information.setMinimumWidth(16) widget.label_information.setMaximumWidth(16) form_widget = QWidget() h_layout = QHBoxLayout() h_layout.addSpacing(4) h_layout.addWidget(widget.label_information, 0, Qt.AlignRight) h_layout.addWidget(widget, 0, Qt.AlignLeft) h_layout.addWidget(QLabel(hint or ''), 0, Qt.AlignLeft) form_widget.setLayout(h_layout) if check: widget.check_value = lambda value: check(value) else: widget.check_value = lambda value: (True, '') if info: label = widget.label_information label = PreferencesDialog.update_icon(label, INFO_ICON) label.setToolTip(info) self.widgets.append(widget) self.widgets_dic[option] = widget self.grid_layout.addWidget( widget.label, self.row, 0, Qt.AlignRight | Qt.AlignCenter ) self.grid_layout.addWidget( form_widget, self.row, 1, Qt.AlignLeft | Qt.AlignCenter ) self.row += 1 def create_textbox(self, label, option, hint=None, check=None, info=None): """Create textbox (QLineEdit) preference option.""" widget = QLineEdit() widget.setAttribute(Qt.WA_MacShowFocusRect, False) widget.setMinimumWidth(250) widget.get_value = lambda w=widget: w.text() widget.set_value = lambda value, w=widget: w.setText(value) widget.set_warning = lambda w=widget: w.setSelection(0, 1000) widget.textChanged.connect( lambda v=None, w=widget: self.options_changed(widget=w) ) self.create_widget( widget=widget, option=option, label=label, hint=hint, check=check, info=info, ) def create_checkbox(self, label, option, check=None, hint=None, info=None): """Create checkbox preference option.""" widget = QCheckBox() widget.get_value = lambda w=widget: bool(w.checkState()) widget.set_value = lambda value, w=widget: bool( w.setCheckState(Qt.Checked if value else Qt.Unchecked) ) api_widget = self.widgets_dic['anaconda_api_url'] widget.set_warning = lambda w=widget: api_widget widget.stateChanged.connect( lambda v=None, w=widget: self.options_changed(widget=w) ) self.create_widget( widget=widget, option=option, label=label, hint=hint, check=check, info=info, ) def options_changed(self, value=None, widget=None): """Callback helper triggered on preference value change.""" config_value = self.get_option(widget.option) if config_value != widget.get_value(): self.widgets_changed.add(widget) else: if widget in self.widgets_changed: self.widgets_changed.remove(widget) self.button_ok.setDisabled(not bool(len(self.widgets_changed))) def widget_for_option(self, option): """Return the widget for the given option.""" return self.widgets_dic[option] # --- API # ------------------------------------------------------------------------- def set_initial_values(self): """ Set configuration values found in other config files. Some options of configuration are found in condarc or in anaconda-client configuration. """ self.config.set( 'main', 'anaconda_api_url', self.api.client_get_api_url() ) # See https://conda.io/docs/install/central.html # ssl_verify overloads True/False/<Path to certificate> # Navigator splits that into 2 separate options for clarity ssl_verify = self.api.client_get_ssl() if isinstance(ssl_verify, bool): self.config.set('main', 'ssl_verification', ssl_verify) self.config.set('main', 'ssl_certificate', None) else: self.config.set('main', 'ssl_verification', True) self.config.set('main', 'ssl_certificate', ssl_verify) def setup(self): """Setup the preferences dialog.""" def api_url_checker(value): """ Custom checker to use selected ssl option instead of stored one. This allows to set an unsafe api url directly on the preferences dialog. Without this, one would have to first disable, click accept, then open preferences again and change api url for it to work. """ # Ssl widget ssl_widget = self.widgets_dic.get('ssl_verification') verify = ssl_widget.get_value() if ssl_widget else True # Certificate path ssl_cert_widget = self.widgets_dic.get('ssl_certificate') if ssl_cert_widget: verify = ssl_cert_widget.get_value() # Offline mode offline_widget = self.widgets_dic.get('offline_mode') if ssl_widget or ssl_cert_widget: offline_mode = offline_widget.get_value() else: offline_mode = False if offline_mode: basic_check = ( False, 'API Domain cannot be modified when ' 'working in <b>offline mode</b>.<br>', ) else: basic_check = self.is_valid_api(value, verify=verify) return basic_check def ssl_checker(value): """Counterpart to api_url_checker.""" api_url_widget = self.widgets_dic.get('anaconda_api_url') api_url = api_url_widget.get_value() return self.is_valid_api(api_url, verify=value) def ssl_certificate_checker(value): """Check if certificate path is valid/exists.""" ssl_widget = self.widgets_dic.get('ssl_verification') verify = ssl_widget.get_value() if ssl_widget else True ssl_cert_widget = self.widgets_dic.get('ssl_certificate') path = ssl_cert_widget.get_value() return self.is_valid_cert_file(path, verify) self.set_initial_values() self.create_textbox( 'Anaconda API domain', 'anaconda_api_url', check=api_url_checker, ) self.create_checkbox( 'Enable SSL verification', 'ssl_verification', check=ssl_checker, hint=( '<i>Disabling this option is not <br>' 'recommended for security reasons</i>' ), ) self.create_textbox( 'SSL certificate path (Optional)', 'ssl_certificate', check=ssl_certificate_checker, ) info = '''To help us improve Anaconda Navigator, fix bugs, and make it even easier for everyone to use Python, we gather anonymized usage information, just like most web browsers and mobile apps.''' self.create_checkbox( 'Quality improvement reporting', 'provide_analytics', info=info, ) info_offline = DialogOfflineMode.MESSAGE_PREFERENCES extra = '<br><br>' if WIN7 else '' self.create_checkbox( 'Enable offline mode', 'offline_mode', info=info_offline + extra, ) self.create_checkbox('Hide offline mode dialog', 'hide_offline_dialog') self.create_checkbox('Hide quit dialog', 'hide_quit_dialog') self.create_checkbox( 'Hide update dialog on startup', 'hide_update_dialog' ) self.create_checkbox( 'Hide running applications dialog', 'hide_running_apps_dialog' ) self.create_checkbox( 'Enable high DPI scaling', 'enable_high_dpi_scaling' ) self.create_checkbox( 'Show application startup error messages', 'show_application_launch_errors' ) ssl_ver_widget = self.widgets_dic.get('ssl_verification') ssl_ver_widget.stateChanged.connect(self.enable_disable_cert) ssl_cert_widget = self.widgets_dic.get('ssl_certificate') ssl_cert_widget.setPlaceholderText( 'Certificate to verify SSL connections' ) # Refresh enabled/disabled status of certificate textbox self.enable_disable_cert() def enable_disable_cert(self, value=None): """Refresh enabled/disabled status of certificate textbox.""" ssl_cert_widget = self.widgets_dic.get('ssl_certificate') if value: value = bool(value) else: ssl_ver_widget = self.widgets_dic.get('ssl_verification') value = bool(ssl_ver_widget.checkState()) ssl_cert_widget.setEnabled(value) @staticmethod def update_icon(label, icon): """Update icon for information or warning.""" pixmap = QPixmap(icon) label.setScaledContents(True) label.setPixmap( pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation) ) return label @staticmethod def warn(widget, text=None): """Display warning for widget in preferences.""" label = widget.label_information if text: label = PreferencesDialog.update_icon(label, WARNING_ICON) label.setToolTip(str(text)) w = widget.label_information.width() / 2 h = widget.label_information.height() / 2 position = widget.label_information.mapToGlobal(QPoint(w, h)) QCursor.setPos(position) else: label.setPixmap(QPixmap()) label.setToolTip('') # --- Checkers # ------------------------------------------------------------------------- def is_valid_url(self, url): """Check if a given URL returns a 200 code.""" output = self.api.download_is_valid_url(url, non_blocking=False) error = '' if not output: error = 'Invalid api url.' return output, error def is_valid_cert_file(self, path, verify): """"Check if ssl certificate file in given path exists.""" output = True error = '' # Only validate if it is not empty and if ssl_verification is checked if path.strip() and verify: output = os.path.isfile(path) if not output: error = 'File not found.' return output, error def is_valid_api(self, url, verify=True): """Check if a given URL is a valid anaconda api endpoint.""" output = self.api.download_is_valid_api_url( url, non_blocking=False, verify=verify, ) error = '' if not output: url_api_1 = '' url_api_2 = '' if '/api' not in url and self.is_valid_url(url)[0]: url_api_1 = url.replace('https://', 'https://api.') url_api_1 = url_api_1.replace('http://', 'http://api.') if url.endswith('/'): url_api_2 = url + 'api' else: url_api_2 = url + '/api' error = ( 'Invalid Anaconda API url. <br>' '<br>Try using:<br><b>{0}</b> or <br>' '<b>{1}</b>'.format(url_api_1, url_api_2) ) else: error = ( 'Invalid Anaconda API url.<br><br>' 'Check the url is valid and corresponds to the api ' 'endpoint.' ) return output, error def run_checks(self): """ Run all check functions on configuration options. This method checks and warns but it does not change/set values. """ checks = [] for widget in self.widgets_changed: value = widget.get_value() check, error = widget.check_value(value) checks.append(check) if check: self.warn(widget) else: self.button_ok.setDisabled(True) widget.set_warning() self.warn(widget, error) break # Emit checks ready self.sig_check_ready.emit() return checks def reset_to_defaults(self): """Reset the preferences to the default values.""" for widget in self.widgets: option = widget.option default = self.get_option_default(option) widget.set_value(default) # Flag all values as updated self.options_changed(widget=widget, value=default) self.sig_reset_ready.emit() def accept(self): """Override Qt method.""" sig_updated = False anaconda_api_url = None checks = self.run_checks() # Update values if checks and all(checks): for widget in self.widgets_changed: value = widget.get_value() self.set_option(widget.option, value) # Settings not stored on Navigator config, but taken from # anaconda-client config if widget.option == 'anaconda_api_url': anaconda_api_url = value # Store it to be emitted self.api.client_set_api_url(value) sig_updated = True # ssl_verify/verify_ssl handles True/False/<Path to cert> # On navi it is split in 2 options for clarity if widget.option in ['ssl_certificate', 'ssl_verification']: ssl_veri = self.widgets_dic.get('ssl_verification') ssl_cert = self.widgets_dic.get('ssl_certificate') verify = ssl_veri.get_value() path = ssl_cert.get_value() if path.strip() and verify: value = path else: value = verify self.api.client_set_ssl(value) if sig_updated and anaconda_api_url: def _api_info(worker, output, error): conda_url = output.get('conda_url') try: self.sig_urls_updated.emit(anaconda_api_url, conda_url) super(PreferencesDialog, self).accept() except RuntimeError: # Some tests on appveyor/circleci fail pass worker = self.api.api_urls() worker.sig_chain_finished.connect(_api_info) super(PreferencesDialog, self).accept()
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 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
def __init__(self, api, parent=None): """Login dialog.""" super(AuthenticationDialog, self).__init__(parent) self._parent = parent self.config = CONF self.api = api self.token = None self.error = None self.tracker = GATracker() self.forgot_username_url = None self.forgot_password_url = None # Widgets self.label_username = QLabel('Username:'******'Password:'******'<hr><br><b>Already a member? ' 'Sign in!</b><br>') # For styling purposes the label next to a ButtonLink is also a button # so they align adequately self.button_register_text = ButtonLabel('You can register by ' 'visiting the ') self.button_register = ButtonLink('Anaconda Cloud') self.button_register_after_text = ButtonLabel('website.') self.label_information = QLabel(''' <strong>Anaconda Cloud</strong> is where packages, notebooks, and <br> environments are shared. It provides powerful <br> collaboration and package management for open <br> source and private projects.<br> ''') self.label_message = QLabel('') self.button_forgot_username = ButtonLink('I forgot my username') self.button_forgot_password = ButtonLink('I forgot my password') self.button_login = ButtonPrimary('Login') self.button_cancel = ButtonNormal('Cancel') # Widgets setup self.button_login.setDefault(True) username_validator = QRegExpValidator(self.USER_RE) self.text_username.setValidator(username_validator) self.setMinimumWidth(260) self.setWindowTitle('Sign in') # This allows to completely style the dialog with css using the frame self.text_password.setEchoMode(QLineEdit.Password) self.label_message.setVisible(False) # Layout grid_layout = QVBoxLayout() grid_layout.addWidget(self.label_username) # grid_layout.addWidget(SpacerVertical()) grid_layout.addWidget(self.text_username) grid_layout.addWidget(SpacerVertical()) grid_layout.addWidget(self.label_password) # grid_layout.addWidget(SpacerVertical()) grid_layout.addWidget(self.text_password) main_layout = QVBoxLayout() main_layout.addWidget(self.label_information) register_layout = QHBoxLayout() register_layout.addWidget(self.button_register_text) register_layout.addWidget(self.button_register) register_layout.addWidget(self.button_register_after_text) register_layout.addStretch() main_layout.addLayout(register_layout) main_layout.addWidget(self.label_signin_text) main_layout.addLayout(grid_layout) main_layout.addWidget(SpacerVertical()) main_layout.addWidget(self.label_message) main_layout.addWidget(self.button_forgot_username, 0, Qt.AlignRight) main_layout.addWidget(self.button_forgot_password, 0, Qt.AlignRight) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_login) main_layout.addWidget(SpacerVertical()) main_layout.addWidget(SpacerVertical()) main_layout.addLayout(layout_buttons) self.setLayout(main_layout) # Signals self.text_username.textEdited.connect(self.check_text) self.text_password.textEdited.connect(self.check_text) self.button_login.clicked.connect(self.login) self.button_cancel.clicked.connect(self.reject) # Setup self.check_text() self.update_style_sheet() self.text_username.setFocus() self.setup()
class AuthenticationDialog(DialogBase): """Login dialog.""" # See https://github.com/Anaconda-Platform/anaconda-server settings USER_RE = QRegExp('^[A-Za-z0-9_][A-Za-z0-9_-]+$') FORGOT_USERNAME_URL = 'account/forgot_username' FORGOT_PASSWORD_URL = 'account/forgot_password' sig_authentication_succeeded = Signal() sig_authentication_failed = Signal() sig_url_clicked = Signal(object) def __init__(self, api, parent=None): """Login dialog.""" super(AuthenticationDialog, self).__init__(parent) self._parent = parent self.config = CONF self.api = api self.token = None self.error = None self.tracker = GATracker() self.forgot_username_url = None self.forgot_password_url = None # Widgets self.label_username = QLabel('Username:'******'Password:'******'<hr><br><b>Already a member? ' 'Sign in!</b><br>') # For styling purposes the label next to a ButtonLink is also a button # so they align adequately self.button_register_text = ButtonLabel('You can register by ' 'visiting the ') self.button_register = ButtonLink('Anaconda Cloud') self.button_register_after_text = ButtonLabel('website.') self.label_information = QLabel(''' <strong>Anaconda Cloud</strong> is where packages, notebooks, and <br> environments are shared. It provides powerful <br> collaboration and package management for open <br> source and private projects.<br> ''') self.label_message = QLabel('') self.button_forgot_username = ButtonLink('I forgot my username') self.button_forgot_password = ButtonLink('I forgot my password') self.button_login = ButtonPrimary('Login') self.button_cancel = ButtonNormal('Cancel') # Widgets setup self.button_login.setDefault(True) username_validator = QRegExpValidator(self.USER_RE) self.text_username.setValidator(username_validator) self.setMinimumWidth(260) self.setWindowTitle('Sign in') # This allows to completely style the dialog with css using the frame self.text_password.setEchoMode(QLineEdit.Password) self.label_message.setVisible(False) # Layout grid_layout = QVBoxLayout() grid_layout.addWidget(self.label_username) # grid_layout.addWidget(SpacerVertical()) grid_layout.addWidget(self.text_username) grid_layout.addWidget(SpacerVertical()) grid_layout.addWidget(self.label_password) # grid_layout.addWidget(SpacerVertical()) grid_layout.addWidget(self.text_password) main_layout = QVBoxLayout() main_layout.addWidget(self.label_information) register_layout = QHBoxLayout() register_layout.addWidget(self.button_register_text) register_layout.addWidget(self.button_register) register_layout.addWidget(self.button_register_after_text) register_layout.addStretch() main_layout.addLayout(register_layout) main_layout.addWidget(self.label_signin_text) main_layout.addLayout(grid_layout) main_layout.addWidget(SpacerVertical()) main_layout.addWidget(self.label_message) main_layout.addWidget(self.button_forgot_username, 0, Qt.AlignRight) main_layout.addWidget(self.button_forgot_password, 0, Qt.AlignRight) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_login) main_layout.addWidget(SpacerVertical()) main_layout.addWidget(SpacerVertical()) main_layout.addLayout(layout_buttons) self.setLayout(main_layout) # Signals self.text_username.textEdited.connect(self.check_text) self.text_password.textEdited.connect(self.check_text) self.button_login.clicked.connect(self.login) self.button_cancel.clicked.connect(self.reject) # Setup self.check_text() self.update_style_sheet() self.text_username.setFocus() self.setup() def setup(self): """Setup login dialog.""" self.update_links() def update_links(self): """Update links.""" for button in [ self.button_forgot_username, self.button_forgot_password, self.button_register, ]: try: button.disconnect() except TypeError: # pragma: no cover pass # TODO, get this from anaconda client directly? # from binstar_client.utils import get_config, set_config # config = get_config() anaconda_api_url = self.config.get('main', 'anaconda_api_url', None) if anaconda_api_url: # Remove api if using a subdomain base_url = anaconda_api_url.lower().replace('//api.', '//') self.base_url = base_url # Remove api if not using a subdomain parts = base_url.lower().split('/') if parts[-1] == 'api': base_url = '/'.join(parts[:-1]) self.forgot_username_url = (base_url + '/' + self.FORGOT_USERNAME_URL) self.forgot_password_url = (base_url + '/' + self.FORGOT_PASSWORD_URL) self.button_register.clicked.connect( lambda: self.open_url(base_url)) self.button_forgot_username.clicked.connect( lambda: self.open_url(self.forgot_username_url)) self.button_forgot_password.clicked.connect( lambda: self.open_url(self.forgot_password_url)) @property def username(self): """Return the logged username.""" return self.text_username.text().lower() def update_style_sheet(self, style_sheet=None): """Update custom css style sheet.""" if style_sheet is None: style_sheet = load_style_sheet() self.setStyleSheet(style_sheet) def check_text(self): """Check that `username` and `password` are valid. If not empty and disable/enable buttons accordingly. """ username = self.text_username.text() password = self.text_password.text() if len(username) == 0 or len(password) == 0: self.button_login.setDisabled(True) else: self.button_login.setDisabled(False) def login(self): """Try to log the user in the specified anaconda api endpoint.""" self.button_login.setEnabled(False) self.text_username.setText(self.text_username.text().lower()) QApplication.setOverrideCursor(Qt.WaitCursor) self.label_message.setText('') worker = self.api.login(self.text_username.text().lower(), self.text_password.text()) worker.sig_finished.connect(self._finished) def _finished(self, worker, output, error): """Callback for the login procedure after worker has finished.""" token = output if token: self.token = token self.sig_authentication_succeeded.emit() self.accept() elif error: username = self.text_username.text().lower() bold_username = '******'.format(username) # The error might come in (error_message, http_error) format try: error_message = ast.literal_eval(str(error))[0] except Exception: # pragma: no cover error_message = str(error) error_message = error_message.lower().capitalize() error_message = error_message.split(', ')[0] error_text = '<i>{0}</i>'.format(error_message) error_text = error_text.replace(username, bold_username) self.label_message.setText(error_text) self.label_message.setVisible(True) if error_message: domain = self.api.client_domain() label = '{0}/{1}: {2}'.format(domain, username, error_message.lower()) self.tracker.track_event( 'authenticate', 'login failed', label=label, ) self.text_password.setFocus() self.text_password.selectAll() self.sig_authentication_failed.emit() self.button_login.setDisabled(False) self.check_text() QApplication.restoreOverrideCursor() def open_url(self, url): """Open given url in the default browser and log the action.""" self.tracker.track_event('content', 'click', url) self.sig_url_clicked.emit(url) QDesktopServices.openUrl(QUrl(url))
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)
class CreateDialog(DialogBase): """Create new project dialog.""" def __init__(self, parent=None, projects=None): """Create new environment dialog.""" super(CreateDialog, self).__init__(parent=parent) self.projects = projects # Widgets self.label_name = QLabel("Project name") self.text_name = QLineEdit() self.button_ok = ButtonPrimary('Create') self.button_cancel = ButtonNormal('Cancel') # Widgets setup self.text_name.setPlaceholderText("New project name") self.setMinimumWidth(380) self.setWindowTitle("Create new project") self.text_name.setValidator(get_regex_validator()) # Layouts grid = QGridLayout() grid.addWidget(self.label_name, 0, 0) grid.addWidget(SpacerHorizontal(), 0, 1) grid.addWidget(self.text_name, 0, 2) grid.addWidget(SpacerVertical(), 1, 0) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) main_layout = QVBoxLayout() main_layout.addLayout(grid) main_layout.addWidget(SpacerVertical()) main_layout.addWidget(SpacerVertical()) main_layout.addLayout(layout_buttons) self.setLayout(main_layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.text_name.textChanged.connect(self.refresh) # Setup self.refresh() def refresh(self, text=''): """Update status of buttons based on combobox selection.""" self.button_ok.setDisabled(True) text = self.text_name.text().strip() if self.projects is not None: if is_valid_project_name(text, self.projects): self.button_ok.setDisabled(False) else: self.button_ok.setDisabled(True) @property def name(self): """Return the project name.""" return self.text_name.text().strip()
class ImportDialog(DialogBase): """Import project from folder or specification files.""" CONDA_ENV_FILES = 'Conda environment files (*.yaml *.yml)' CONDA_SPEC_FILES = 'Conda explicit specification files (*.txt)' PIP_REQUIREMENT_FILES = 'Pip requirement files (*.txt)' def __init__(self, parent=None, projects=None): """Import project from folder or environment files.""" super(ImportDialog, self).__init__(parent=parent) self.projects = projects if projects else {} self.selected_file_filter = None self._path = None # Widgets self.label_info = LabelSpecInfo('', parent=self) self.label_name = QLabel("Project name") self.label_path = QLabel("Specification File") self.text_name = QLineEdit() self.text_path = QLineEdit() self.button_path = ButtonNormal("") self.radio_folder = QRadioButton('From folder') self.radio_spec = QRadioButton('From specification file') self.button_cancel = ButtonNormal('Cancel') self.button_ok = ButtonPrimary('Import') # Widgets setup self.button_path.setObjectName('import') self.button_ok.setDefault(True) self.text_path.setPlaceholderText("File to import from") self.text_name.setPlaceholderText("New project name") self.setMinimumWidth(380) self.setWindowTitle("Import new project") self.text_name.setValidator(get_regex_validator()) # Layouts layout_radio = QHBoxLayout() layout_radio.addWidget(self.radio_folder) layout_radio.addWidget(SpacerHorizontal()) layout_radio.addWidget(self.radio_spec) layout_infile = QHBoxLayout() layout_infile.addWidget(self.text_path) layout_infile.addWidget(SpacerHorizontal()) layout_infile.addWidget(self.button_path) layout_grid = QGridLayout() layout_grid.addWidget(self.label_name, 0, 0, 1, 2) layout_grid.addWidget(SpacerHorizontal(), 0, 2) layout_grid.addWidget(self.text_name, 0, 3) layout_grid.addWidget(SpacerVertical(), 1, 0) layout_grid.addWidget(self.label_path, 2, 0) layout_grid.addWidget(self.label_info, 2, 1) layout_grid.addWidget(SpacerHorizontal(), 2, 2) layout_grid.addLayout(layout_infile, 2, 3) 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.addLayout(layout_radio) layout.addWidget(SpacerVertical()) layout.addLayout(layout_grid) 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_path.clicked.connect(self.choose) self.text_path.textChanged.connect(self.refresh) self.text_name.textChanged.connect(self.refresh) self.radio_folder.toggled.connect(self.refresh) self.radio_spec.toggled.connect(self.refresh) # Setup self.radio_folder.toggle() self.refresh() def refresh(self, text=''): """Update the status of buttons based on radio selection.""" if self.radio_folder.isChecked(): self.text_path.setPlaceholderText("Folder to import from") self.label_path.setText('Folder') self.label_info.setVisible(False) else: self.label_info.setVisible(True) self.label_path.setText('File ') self.text_path.setPlaceholderText("File to import from") text = self.text_name.text() path = self.text_path.text() if (text and path and os.path.exists(path) and is_valid_project_name(text, self.projects)): self.button_ok.setDisabled(False) self.button_ok.setDefault(True) else: self.button_ok.setDisabled(True) self.button_cancel.setDefault(True) def choose(self): """Display file dialog to select environment specification.""" selected_filter = None if self.radio_spec.isChecked(): path, selected_filter = getopenfilename( caption="Import Project", basedir=HOME_PATH, parent=None, filters="{0};;{1};;{2}".format(self.CONDA_ENV_FILES, self.CONDA_SPEC_FILES, self.PIP_REQUIREMENT_FILES)) else: path = getexistingdirectory( caption="Import Project", basedir=HOME_PATH, parent=None, ) if path: name = self.text_name.text() self.selected_file_filter = selected_filter self.text_path.setText(path) self.refresh(path) self.text_name.setText(name) @property def name(self): """Return the project name.""" return self.text_name.text().strip() @property def path(self): """Return the project path to import (file or folder).""" return self.text_path.text()
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 ProjectsPathDialog(DialogBase): """Select project path.""" def __init__(self, parent=None, default=DEFAULT_PROJECTS_PATH): """Select project folder.""" super(ProjectsPathDialog, self).__init__(parent=parent) # Widgets self.label_description = QLabel("If no path is selected, the default" " one will be used.") self.label_name = QLabel("Select the projects folder to use") self.label_path = QLabel("Projects path") self.label_info = QLabel('') self.text_path = QLineEdit() self.button_path = ButtonNormal("") self.button_default = ButtonNormal('Use default') self.button_ok = ButtonPrimary('Select') # Widgets setup self.button_path.setObjectName('import') self.default = default self.text_path.setPlaceholderText("projects folder path") self.text_path.setReadOnly(True) self.setMinimumWidth(580) self.setWindowTitle("Select Projects Path") # Layouts layout_grid = QGridLayout() layout_grid.addWidget(self.label_path, 0, 0) layout_grid.addWidget(SpacerHorizontal(), 0, 1) layout_grid.addWidget(self.text_path, 0, 2) layout_grid.addWidget(SpacerHorizontal(), 0, 3) layout_grid.addWidget(self.button_path, 0, 4) layout_grid.addWidget(SpacerVertical(), 1, 0, 1, 4) layout_grid.addWidget(self.label_info, 2, 2, 1, 2) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_default) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout = QVBoxLayout() layout.addWidget(self.label_description) layout.addWidget(SpacerVertical()) layout.addLayout(layout_grid) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_default.clicked.connect(self.use_default) self.button_path.clicked.connect(self.choose) # Setup self.refresh() @property def path(self): """Return the folder project path.""" return self.text_path.text().strip() or self.default def use_default(self): """Set the default projects path as the folder path to use.""" self.text_path.setText('') self.accept() def choose(self): """Display directory dialog to select project folder.""" path = getexistingdirectory( caption="Select Projects Folder", basedir=self.default, parent=self, ) # Enforce the correct slashes. # See Issue https://github.com/ContinuumIO/navigator/issues/1164 if WIN: path = path.replace('/', '\\') else: path = path.replace('\\', '/') if path: self.text_path.setText(path) self.refresh() return path def is_valid_path(self): """Check that entered path is valid.""" check = False error = '' path = self.text_path.text().strip() if bool(path) and os.path.isdir(path): if ' ' in path: error = '<b>Please select a path without spaces</b>' elif path == HOME_PATH: error = ('<b>Please select a path different to the home ' 'directory.</b>') elif not path_is_writable(path): error = ('<b>Please select a path that has write access.</b>') check = (' ' not in path and path != HOME_PATH and path_is_writable(path)) return check, error def refresh(self): """Refresh button status based on path.""" check, error = self.is_valid_path() self.button_ok.setEnabled(check) if check or not bool(self.text_path.text()): self.label_info.setText('<i>Default: {0}</i>'.format(self.default)) else: self.label_info.setText(error) def reject(self): """Override Qt method.""" self.use_default()