class ProgressView(QWidget): def __init__(self, parent=None) -> None: super(ProgressView, self).__init__(parent) self._progress_tree_view = QTreeView(self) self._progress_tree_view.setHeaderHidden(True) self._progress_tree_view.setItemsExpandable(False) self._progress_tree_view.setItemDelegate(ProgressDelegate(self)) self._progress_tree_view.setRootIsDecorated(False) self._progress_tree_view.setFixedHeight(30) self._progress_tree_view.setVerticalScrollBarPolicy( Qt.ScrollBarAlwaysOff) self._progress_bar = QProgressBar(self) self._progress_bar.setRange(0, 0) self._progress_bar.setFixedHeight(30) self._progress_bar.setVisible(False) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self._progress_tree_view) layout.addWidget(self._progress_bar) self.setLayout(layout) self.setFixedHeight(30) def setModel(self, model) -> None: self._progress_tree_view.setModel(model) @Slot(bool) def setIndeterminate(self, b: bool) -> None: if b: self._progress_bar.setVisible(True) self._progress_tree_view.setVisible(False) else: self._progress_bar.setVisible(False) self._progress_tree_view.setVisible(True)
class HomeTab(WidgetBase): """Home applications tab.""" # name, prefix, sender sig_item_selected = Signal(object, object, object) # button_widget, sender sig_channels_requested = Signal(object, object) # application_name, command, prefix, leave_path_alone, sender sig_launch_action_requested = Signal(object, object, bool, object, object) # action, application_name, version, sender sig_conda_action_requested = Signal(object, object, object, object) # url sig_url_clicked = Signal(object) # TODO: Connect these signals to have more granularity # [{'name': package_name, 'version': version}...], sender sig_install_action_requested = Signal(object, object) sig_remove_action_requested = Signal(object, object) def __init__(self, parent=None): """Home applications tab.""" super(HomeTab, self).__init__(parent) # Variables self._parent = parent self.api = AnacondaAPI() self.applications = None self.style_sheet = None self.app_timers = None self.current_prefix = None # Widgets self.list = ListWidgetApplication() self.button_channels = ButtonHomeChannels('Channels') self.button_refresh = ButtonHomeRefresh('Refresh') self.combo = ComboHomeEnvironment() self.frame_top = FrameTabHeader(self) self.frame_body = FrameTabContent(self) self.frame_bottom = FrameTabFooter(self) self.label_home = LabelHome('Applications on') self.label_status_action = QLabel('') self.label_status = QLabel('') self.progress_bar = QProgressBar() self.first_widget = self.combo # Widget setup self.setObjectName('Tab') self.progress_bar.setTextVisible(False) self.list.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) # Layout layout_top = QHBoxLayout() layout_top.addWidget(self.label_home) layout_top.addWidget(SpacerHorizontal()) layout_top.addWidget(self.combo) layout_top.addWidget(SpacerHorizontal()) layout_top.addWidget(self.button_channels) layout_top.addWidget(SpacerHorizontal()) layout_top.addStretch() layout_top.addWidget(self.button_refresh) self.frame_top.setLayout(layout_top) layout_body = QVBoxLayout() layout_body.addWidget(self.list) self.frame_body.setLayout(layout_body) layout_bottom = QHBoxLayout() layout_bottom.addWidget(self.label_status_action) layout_bottom.addWidget(SpacerHorizontal()) layout_bottom.addWidget(self.label_status) layout_bottom.addStretch() layout_bottom.addWidget(self.progress_bar) self.frame_bottom.setLayout(layout_bottom) layout = QVBoxLayout() layout.addWidget(self.frame_top) layout.addWidget(self.frame_body) layout.addWidget(self.frame_bottom) self.setLayout(layout) # Signals self.list.sig_conda_action_requested.connect( self.sig_conda_action_requested) self.list.sig_url_clicked.connect(self.sig_url_clicked) self.list.sig_launch_action_requested.connect( self.sig_launch_action_requested) self.button_channels.clicked.connect(self.show_channels) self.button_refresh.clicked.connect(self.refresh_cards) self.progress_bar.setVisible(False) # --- Setup methods # ------------------------------------------------------------------------- def setup(self, conda_data): """Setup the tab content.""" conda_processed_info = conda_data.get('processed_info') environments = conda_processed_info.get('__environments') applications = conda_data.get('applications') self.current_prefix = conda_processed_info.get('default_prefix') self.set_environments(environments) self.set_applications(applications) def set_environments(self, environments): """Setup the environments list.""" # Disconnect to avoid triggering the signal when updating the content try: self.combo.currentIndexChanged.disconnect() except TypeError: pass self.combo.clear() for i, (env_prefix, env_name) in enumerate(environments.items()): self.combo.addItem(env_name, env_prefix) self.combo.setItemData(i, env_prefix, Qt.ToolTipRole) index = 0 for i, (env_prefix, env_name) in enumerate(environments.items()): if self.current_prefix == env_prefix: index = i break self.combo.setCurrentIndex(index) self.combo.currentIndexChanged.connect(self._item_selected) def set_applications(self, applications): """Build the list of applications present in the current conda env.""" apps = self.api.process_apps(applications, prefix=self.current_prefix) all_applications = [] installed_applications = [] not_installed_applications = [] # Check if some installed applications are not on the apps dict # for example when the channel was removed. linked_apps = self.api.conda_linked_apps_info(self.current_prefix) missing_apps = [app for app in linked_apps if app not in apps] for app in missing_apps: apps[app] = linked_apps[app] for app_name in sorted(list(apps.keys())): app = apps[app_name] item = ListItemApplication(name=app['name'], description=app['description'], versions=app['versions'], command=app['command'], image_path=app['image_path'], prefix=self.current_prefix, needs_license=app.get( 'needs_license', False)) if item.installed: installed_applications.append(item) else: not_installed_applications.append(item) all_applications = installed_applications + not_installed_applications self.list.clear() for i in all_applications: self.list.addItem(i) self.list.update_style_sheet(self.style_sheet) self.set_widgets_enabled(True) self.update_status() # --- Other methods # ------------------------------------------------------------------------- def current_environment(self): """Return the current selected environment.""" env_name = self.combo.currentText() return self.api.conda_get_prefix_envname(env_name) def refresh_cards(self): """Refresh application widgets. List widget items sometimes are hidden on resize. This method tries to compensate for that refreshing and repainting on user demand. """ self.list.update_style_sheet(self.style_sheet) self.list.repaint() for item in self.list.items(): if not item.widget.isVisible(): item.widget.repaint() def show_channels(self): """Emit signal requesting the channels dialog editor.""" self.sig_channels_requested.emit(self.button_channels, C.TAB_HOME) def update_list(self, name=None, version=None): """Update applications list.""" self.set_applications() self.label_status.setVisible(False) self.label_status_action.setVisible(False) self.progress_bar.setVisible(False) def update_versions(self, apps=None): """Update applications versions.""" self.items = [] for i in range(self.list.count()): item = self.list.item(i) self.items.append(item) if isinstance(item, ListItemApplication): name = item.name meta = apps.get(name) if meta: versions = meta['versions'] version = self.api.get_dev_tool_version(item.path) item.update_versions(version, versions) # --- Common Helpers (# FIXME: factor out to common base widget) # ------------------------------------------------------------------------- def _item_selected(self, index): """Notify that the item in combo (environment) changed.""" name = self.combo.itemText(index) prefix = self.combo.itemData(index) self.sig_item_selected.emit(name, prefix, C.TAB_HOME) @property def last_widget(self): """Return the last element of the list to be used in tab ordering.""" if self.list.items(): return self.list.items()[-1].widget def ordered_widgets(self, next_widget=None): """Return a list of the ordered widgets.""" ordered_widgets = [ self.combo, self.button_channels, self.button_refresh, ] ordered_widgets += self.list.ordered_widgets() return ordered_widgets def set_widgets_enabled(self, value): """Enable or disable widgets.""" self.combo.setEnabled(value) self.button_channels.setEnabled(value) self.button_refresh.setEnabled(value) for item in self.list.items(): item.button_install.setEnabled(value) item.button_options.setEnabled(value) if value: item.set_loading(not value) def update_items(self): """Update status of items in list.""" if self.list: for item in self.list.items(): item.update_status() def update_status(self, action='', message='', value=None, max_value=None): """Update the application action status.""" # Elide if too big width = QApplication.desktop().availableGeometry().width() max_status_length = round(width * (2.0 / 3.0), 0) msg_percent = 0.70 fm = self.label_status_action.fontMetrics() action = fm.elidedText(action, Qt.ElideRight, round(max_status_length * msg_percent, 0)) message = fm.elidedText( message, Qt.ElideRight, round(max_status_length * (1 - msg_percent), 0)) self.label_status_action.setText(action) self.label_status.setText(message) if max_value is None and value is None: self.progress_bar.setVisible(False) else: self.progress_bar.setVisible(True) self.progress_bar.setMaximum(max_value) self.progress_bar.setValue(value) def update_style_sheet(self, style_sheet=None): """Update custom CSS style sheet.""" if style_sheet is None: self.style_sheet = load_style_sheet() else: self.style_sheet = style_sheet self.list.update_style_sheet(style_sheet=self.style_sheet) self.setStyleSheet(self.style_sheet)
class MainWindow(QMainWindow): sig_logged_in = Signal() sig_logged_out = Signal() DOCS_URL = 'https://docs.continuum.io/anaconda/navigator' VIDEOS_URL = "http://content.continuum.io/api/videos" EVENTS_URL = "http://content.continuum.io/api/events" WEBINARS_URL = "http://content.continuum.io/api/webinars" def __init__(self, splash=None): super(MainWindow, self).__init__() self.tracker = None self.splash = splash # Anaconda API self.api = AnacondaAPI() self.busy = False self.logged = False self.username = '' self._login_text = 'Sign in to Anaconda Cloud' self.first_run = CONF.get('main', 'first_run') self.application_update_version = None # Widgets self.frame_header = FrameHeader(self) self.frame_body = FrameBody(self) self.label_logo = LabelHeaderLogo('ANACONDA NAVIGATOR') self.button_logged_text = ButtonLabelLogin('') self.button_logged_username = ButtonLinkLogin('') self.label_update_available = LabelHeaderUpdate('Update available!') self.button_update_available = ButtonHeaderUpdate('Update') self.button_login = ButtonLogin(self._login_text) self.central_widget = QWidget() self.statusbar = self.statusBar() self.progressbar = QProgressBar() self.stack = TabWidgetBody(self) self.home_tab = HomeTab(parent=self) self.environments_tab = EnvironmentsTab(parent=self) self.learning_tab = CommunityTab( parent=self, tags=['webinar', 'documentation', 'video', 'training'], content_urls=[self.VIDEOS_URL, self.WEBINARS_URL]) self.community_tab = CommunityTab(parent=self, tags=['event', 'forum', 'social'], content_urls=[self.EVENTS_URL]) # self.projects_tab = ProjectsTab(parent=self) # Note: Icons are set in CSS self.stack.addTab(self.home_tab, text='Home') self.stack.addTab(self.environments_tab, text='Environments') self.stack.addTab(self.learning_tab, text='Learning') self.stack.addTab(self.community_tab, text='Community') # self.stack.addTab(self.projects_tab, 'Projects') # Widget setup self.button_login.setDefault(True) self.label_logo.setPixmap(QPixmap(images.ANACONDA_NAVIGATOR_LOGO)) self.setWindowTitle("Anaconda Navigator") self.statusbar.addPermanentWidget(self.progressbar) self.progressbar.setVisible(False) # Layout header_layout = QHBoxLayout() header_layout.addWidget(self.label_logo) header_layout.addSpacing(18) header_layout.addWidget(self.label_update_available, 0, Qt.AlignCenter) header_layout.addWidget(self.button_update_available, 0, Qt.AlignCenter) header_layout.addStretch() header_layout.addWidget(self.button_logged_text, 0, Qt.AlignTrailing) header_layout.addWidget(self.button_logged_username, 0, Qt.AlignTrailing) header_layout.addWidget(self.button_login, 0, Qt.AlignTrailing) header_layout.setContentsMargins(0, 0, 0, 0) self.frame_header.setLayout(header_layout) body_layout = QHBoxLayout() body_layout.addWidget(self.stack) body_layout.setContentsMargins(0, 0, 0, 0) self.frame_body.setLayout(body_layout) main_layout = QVBoxLayout() main_layout.addWidget(self.frame_header) main_layout.addWidget(self.frame_body) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) self.central_widget.setLayout(main_layout) self.setContentsMargins(0, 0, 0, 0) self.setCentralWidget(self.central_widget) # Signals self.button_login.clicked.connect(self.login) self.button_logged_username.clicked.connect(self.open_login_page) self.button_update_available.clicked.connect(self.update_application) self.stack.currentChanged.connect(self._track_tab) # This needs to be reworked! # self.projects_tab.sig_apps_updated.connect( # self.home_tab.set_applications) # self.projects_tab.sig_apps_changed.connect( # self.home_tab.set_applications) # self.projects_tab.sig_project_updated.connect( # self.home_tab.set_applications) # self.projects_tab.sig_status_updated.connect(self.update_status_bar) # Setup self.api.set_data_directory(CHANNELS_PATH) self.update_style_sheet() # Helpers # ------------------------------------------------------------------------- def _track_tab(self, index=None): """ Tracks the active tab by index, or set `Home` when no index has been provided. """ if index is None: index = self.stack.currentIndex() text = self.stack.currentText().lower() if self.tracker: page = '/{0}'.format(text) self.tracker.track_page(page) def _metadata_updated(self, worker, path, error): self.set_splash('Updating repodata...') if error: logger.error(str(error)) if path and os.path.isfile(path): with open(path, 'r') as f: data = f.read() try: self._metadata = json.loads(data) except Exception: self._metadata = {} channels = CONF.get('main', 'conda_channels', default=tuple()) if not channels: channels = self.api.conda_get_condarc_channels() CONF.set('main', 'conda_channels', channels) CONF.set('main', 'conda_active_channels', channels) self.api.update_repodata(channels=channels) self.api.sig_repodata_updated.connect(self._repodata_updated) def _repodata_updated(self, paths): self.set_splash('Loading repodata...') self.api.sig_repodata_updated.disconnect(self._repodata_updated) if self.first_run: self.set_splash('Initial configuration...') self.api.create_default_project() worker = self.api.client_load_repodata(paths, self._metadata) worker.sig_finished.connect(self.create_application_projects) # --- Public API # ------------------------------------------------------------------------- def setup(self): """ Perform initial setup and configuration. """ self.set_splash('Updating metadata...') user = self.api.client_set_domain() self.update_login_status(user) self.setup_toolbars() self.set_application_icon() worker = self.api.update_metadata() worker.sig_finished.connect(self._metadata_updated) statusbar = self.statusBar() statusbar.setVisible(False) statusbar.setMaximumHeight(0) statusbar.hide() def create_application_projects(self, worker, output, error): if error: logger.error(str(error)) packages, apps = output self.api.create_application_projects( apps, add_project=self.first_run, ) self.post_setup(apps) self.check_for_updates(packages) def post_setup(self, apps): CONF.set('main', 'first_run', False) self.set_splash('Loading applications...') self.home_tab.setup_tab(apps) # self.set_splash('Loading projects...') # self.projects_tab.setup_tab() self.set_splash('Loading environments...') self.environments_tab.setup_tab(metadata=self._metadata) self.set_splash('Loading content...') self.community_tab.setup_tab() self.set_splash('Loading content...') self.learning_tab.setup_tab() self.update_style_sheet() self.showMaximized() self.post_visible_setup() def set_application_icon(self): """ """ app = QCoreApplication.instance() app_icon = QIcon() app_icon.addFile(images.ANACONDA_ICON_16_PATH, QSize(16, 16)) app_icon.addFile(images.ANACONDA_ICON_24_PATH, QSize(24, 24)) app_icon.addFile(images.ANACONDA_ICON_32_PATH, QSize(32, 32)) app_icon.addFile(images.ANACONDA_ICON_48_PATH, QSize(48, 48)) app_icon.addFile(images.ANACONDA_ICON_256_PATH, QSize(256, 256)) app.setWindowIcon(app_icon) def setup_toolbars(self): menubar = self.menuBar() file_menu = menubar.addMenu('&File') file_menu.addAction( create_action(self, "&Preferences", triggered=self.show_preferences, shortcut="Ctrl+P")) file_menu.addAction( create_action(self, "&Quit", triggered=self.close, shortcut="Ctrl+Q")) helpmenu = menubar.addMenu('&Help') helpmenu.addAction( create_action(self, "&Online Documentation", triggered=lambda: self.open_url(self.DOCS_URL))) helpmenu.addAction( create_action(self, "&Logs viewer", triggered=self.show_log_viewer, shortcut="F6")) helpmenu.addSeparator() helpmenu.addAction( create_action(self, "&About", triggered=self.show_about)) def post_visible_setup(self): if self.splash: self.splash.hide() CONF.set('main', 'first_run', False) # Start the tracker only after post_visible_setup self.tracker = GATracker() self._track_tab(0) # Start tracking home self.fix_tab_ordering() self.show_welcome_screen() def check_for_updates(self, packages=None): # Check if there is an update for navigator! version = self.api.conda_package_version(name='root', pkg='anaconda-navigator') # Temporal mock test # mock_versions = [version, '1.1.0'] # packages['anaconda-navigator'] = {'versions': mock_versions} self.button_update_available.setVisible(False) self.label_update_available.setVisible(False) text = '' if packages: package_data = packages.get('anaconda-navigator') if package_data: versions = package_data.get('versions') if versions and version != versions[-1]: self.application_update_version = versions[-1] self.label_update_available.setText(text) self.label_update_available.setVisible(True) self.button_update_available.setVisible(True) def fix_tab_ordering(self): return for tab in [self.community_tab, self.learning_tab]: self.setTabOrder(self.stack.tabbar.buttons[-1], tab.filter_widgets[0]) for i in range(len(tab.filter_widgets) - 1): self.setTabOrder(tab.filter_widgets[i], tab.filter_widgets[i + 1]) self.setTabOrder(tab.filter_widgets[-1], tab.text_filter) self.setTabOrder(tab.text_filter.button_icon, tab.list) self.setTabOrder(tab.list, self.button_login) # self.button_login.setFocus() self.setTabOrder(self.stack.tabbar.buttons[-1], self.environments_tab.text_search) self.environments_tab.packages_widget.table_last_row.add_focus_widget( self.button_login) self.setTabOrder(self.environments_tab.packages_widget.table_last_row, self.button_login) def update_style_sheet(self): style_sheet = load_style_sheet() # self.home_tab.update_style_sheet(style_sheet) self.environments_tab.update_style_sheet(style_sheet) # self.community_tab.update_style_sheet(style_sheet) # self.learning_tab.update_style_sheet(style_sheet) self.setStyleSheet(style_sheet) def set_splash(self, message): """ Set splash message. """ if self.splash: self.splash.show_message(message) QApplication.processEvents() # --- Login # ------------------------------------------------------------------------- def update_login_status(self, user_data=None): """ Update login button and information. """ if user_data: self.username = user_data.get('login', '') self.logged = True if self.logged: username = self.username anaconda_api_url = CONF.get('main', 'anaconda_api_url') token = self.api.client_load_token(anaconda_api_url) self.button_logged_text.setText('Signed in as') self.button_logged_username.setText(username) url = "{0}/{1}".format(CONF.get('main', 'conda_url'), username) self.button_logged_username.setToolTip(url) self.button_login.setText('Sign out') self.environments_tab.packages_widget.set_token(token) else: self.button_logged_text.setText('') self.button_logged_username.setText('') self.button_login.setText(self._login_text) QApplication.restoreOverrideCursor() def login(self): """ Open up login dialog or log out depending on logged status. """ if self.logged: QApplication.setOverrideCursor(Qt.WaitCursor) self.api.client_logout() self.api.client_remove_token() self.logged = False self.sig_logged_out.emit() self.tracker.track_event('authenticate', 'logout', label=self.username) else: dlg = AuthenticationDialog(self.api, parent=self) if self.tracker: self.tracker.track_page('/login', pagetitle='Login dialog') if dlg.exec_(): self.api.client_store_token(dlg.token) self.username = dlg.username self.logged = True self.sig_logged_in.emit() if self.tracker: self.tracker.track_event('authenticate', 'login', label=self.username) self._track_tab() self.update_login_status() logger.debug(str((self.logged, self.username))) # --- Dialogs # ------------------------------------------------------------------------- def show_preferences(self): """ Display the preferences dialog and apply the needed actions. """ dlg = PreferencesDialog(self) self.tracker.track_page('/preferences', pagetitle='Preferences dialog') set_domains = self.environments_tab.packages_widget.update_domains set_domains = self.environments_tab.packages_widget.update_domains dlg.sig_urls_updated.connect(set_domains) dlg.sig_urls_updated.connect(lambda au, cu: self.login()) dlg.exec_() self._track_tab() def show_about(self): """ Display the `About` dialog with information on the project. """ dlg = AboutDialog(self) self.tracker.track_page('/about', pagetitle='About dialog') dlg.exec_() self._track_tab() def show_log_viewer(self): """ Display the logs viewer to the user """ dlg = LogViewerDialog() self.tracker.track_page('/logs', pagetitle='Log Viewer Dialog') dlg.exec_() self._track_tab() def show_welcome_screen(self): if getattr(self, 'showme', True) and CONF.get('main', 'show_startup', True): from anaconda_navigator.widgets.splash import FirstSplash self.showme = False self.splash.hide() dlg = FirstSplash() dlg.raise_() dlg.exec_() # --- Update Navigator # ------------------------------------------------------------------------- def _update_application(self, worker, output, error): self.button_update_available.setDisabled(False) if error: text = 'Anaconda Navigator Update error:' dlg = MessageBoxError(text=text, error=error, title='Application Update Error') self.tracker.track_page('/update/error', pagetitle='Update Application Error ' 'Message Box') dlg.exec_() else: text = ('Anaconda Navigator Updated succefully.\n\n' 'Please restart the application') dlg = MessageBoxInformation(text=text, title='Application Update') self.tracker.track_page('/update/successful', pagetitle='Application Update Succesful ' 'Message Box') dlg.exec_() self._track_tab() def update_application(self): version = self.application_update_version if version: dlg = DialogUpdateApplication(version=version) self.tracker.track_page('/update', pagetitle='Update Application Dialog') reply = dlg.exec_() if reply: self.tracker.track_event('application', 'updated', version) self.busy = True pkg = 'anaconda-navigator={}'.format(version) worker = self.api.conda_install(name='root', pkgs=[pkg]) worker.sig_finished.connect(self._update_application) self.button_update_available.setDisabled(True) self._track_tab() def update_status_bar(self, message='', timeout=0, val=-1, max_val=-1): """ """ statusbar = self.statusBar() if val != -1 and max_val != -1: self.progressbar.setVisible(True) self.progressbar.setValue(val) self.progressbar.setMaximum(max_val) else: self.progressbar.setVisible(False) if message: statusbar.showMessage(message, timeout) else: statusbar.clearMessage() statusbar.setVisible(False) statusbar.setMaximumHeight(0) statusbar.hide() # --- Url handling # ------------------------------------------------------------------------- def open_url(self, url): qurl = QUrl(url) QDesktopServices.openUrl(qurl) self.tracker.track_event('help', 'documentation', url) def open_login_page(self): """ """ conda_url = CONF.get('main', 'conda_url') url = "{0}/{1}".format(conda_url, self.username) qurl = QUrl(url) QDesktopServices.openUrl(qurl) self.tracker.track_event('content', 'clicked', url) # --- Qt methods # ------------------------------------------------------------------------- def closeEvent(self, event): """ Catch close event. """ # TODO: check if an update is not in progress or things might break!! # if self.busy: show_dialog = not CONF.get('main', 'hide_quit_dialog') if show_dialog: if self.tracker: self.tracker.track_page('/quit', pagetitle='Quit dialog') dlg = QuitApplicationDialog() reply = dlg.exec_() if not reply: event.ignore() self._track_tab() def keyPressEvent(self, event): """ Qt override. """ # if event.key() in [Qt.Key_F5]: # self.update_style_sheet() super(MainWindow, self).keyPressEvent(event) def paintEvent(self, event): """ Qt override. Draw lower left border of the main Stacked Widget. """ super(MainWindow, self).paintEvent(event) tab = self.stack.tabbar tab_pos = self.mapTo(self, tab.pos()) pane_pos = self.mapTo(self, self.stack.pos()) stack_height = self.stack.height() menu_height = self.menuBar().height() header_height = 49 # From css padding = 20 # From css left = 1 # From css extra = 8 # Still wondering where this extra Y delta is deltay = menu_height + header_height + tab.height() + padding + extra x0 = tab_pos.x() + tab.width() + padding - left y0 = tab_pos.y() + deltay y1 = pane_pos.y() + stack_height + deltay - tab.height() - padding painter = QPainter(self) painter.setPen(QPen(QColor('#006f43'), 1, Qt.SolidLine, Qt.RoundCap)) painter.drawLine(x0, y0, x0, y1)
class MainWindow(QMainWindow): def __init__(self, title): super().__init__() self.setWindowTitle(title) self.lib_path = "" self.final_title = "" analysis_icon = QIcon(os.path.join(icons_dir, "icon.png")) stack_icon = QIcon(os.path.join(icons_dir, "icon_stack.png")) self.analysis_button = QToolButton(self) self.analysis_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.analysis_button.setIcon(analysis_icon) # TODO use more general solution for text wrapping self.analysis_button.setText(SEGMENTATION_NAME.replace(" ", "\n")) self.analysis_button.setIconSize(QSize(100, 100)) self.mask_button = QToolButton(self) self.mask_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mask_button.setIcon(stack_icon) self.mask_button.setText(MASK_NAME.replace(" ", "\n")) self.mask_button.setIconSize(QSize(100, 100)) self.analysis_button.clicked.connect(self.launch_analysis) self.mask_button.clicked.connect(self.launch_mask) self.progress = QProgressBar() self.progress.setHidden(True) layout = QGridLayout() layout.addWidget(self.progress, 0, 0, 1, 2) layout.addWidget(self.analysis_button, 1, 1) layout.addWidget(self.mask_button, 1, 0) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) self.setWindowIcon(analysis_icon) self.prepare = None self.wind = None def _launch_begin(self): self.progress.setVisible(True) self.progress.setRange(0, 0) self.analysis_button.setDisabled(True) self.mask_button.setDisabled(True) import_config() def launch_analysis(self): self._launch_begin() self.lib_path = "PartSeg.segmentation_analysis.main_window" self.final_title = "PartSeg Segmentation Analysis" self.prepare = Prepare(self.lib_path) self.prepare.finished.connect(self.launch) self.prepare.start() def launch_mask(self): self._launch_begin() self.lib_path = "PartSeg.segmentation_mask.main_window" self.final_title = "PartSeg Mask Segmentation" self.prepare = Prepare(self.lib_path) self.prepare.finished.connect(self.launch) self.prepare.start() def window_shown(self): self.close() def launch(self): if self.prepare.result is None: self.close() return if self.prepare.errors: errors_message = QMessageBox() errors_message.setText("There are errors during start") errors_message.setInformativeText( "During load saved state some of data could not be load properly\n" "The files has prepared backup copies in state directory (Help > State directory)" ) errors_message.setStandardButtons(QMessageBox.Ok) text = "\n".join(["File: " + x[0] + "\n" + str(x[1]) for x in self.prepare.errors]) errors_message.setDetailedText(text) errors_message.exec() wind = self.prepare.result(title=self.final_title, signal_fun=self.window_shown) wind.show() self.wind = wind
class MainWindow(QMainWindow): def __init__(self, title): super().__init__() self.setWindowTitle(title) self.lib_path = "" self.final_title = "" analysis_icon = QIcon(os.path.join(icons_dir, "icon.png")) stack_icon = QIcon(os.path.join(icons_dir, "icon_stack.png")) self.analysis_button = QToolButton(self) self.analysis_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.analysis_button.setIcon(analysis_icon) # TODO use more general solution for text wrapping self.analysis_button.setText(ANALYSIS_NAME.replace(" ", "\n")) self.analysis_button.setIconSize(QSize(100, 100)) self.mask_button = QToolButton(self) self.mask_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.mask_button.setIcon(stack_icon) self.mask_button.setText(MASK_NAME.replace(" ", "\n")) self.mask_button.setIconSize(QSize(100, 100)) self.analysis_button.clicked.connect(self.launch_analysis) self.mask_button.clicked.connect(self.launch_mask) self.progress = QProgressBar() self.progress.setHidden(True) layout = QGridLayout() layout.addWidget(self.progress, 0, 0, 1, 2) layout.addWidget(self.analysis_button, 1, 1) layout.addWidget(self.mask_button, 1, 0) widget = QWidget() widget.setLayout(layout) self.setCentralWidget(widget) self.setWindowIcon(analysis_icon) self.prepare = None self.wind = None self._update_theme() def _update_theme(self): napari_settings = napari_get_settings(state_store.save_folder) with warnings.catch_warnings(): warnings.simplefilter("ignore", FutureWarning) theme = get_theme(napari_settings.appearance.theme) # TODO understand qss overwrite mechanism self.setStyleSheet(napari_template(get_stylesheet(), **theme)) def _launch_begin(self): self.progress.setVisible(True) self.progress.setRange(0, 0) self.analysis_button.setDisabled(True) self.mask_button.setDisabled(True) import_config() def launch_analysis(self): self._launch_begin() self._launch_analysis() self.prepare.start() def _launch_analysis(self): self.lib_path = "PartSeg._roi_analysis.main_window" self.final_title = f"{APP_NAME} {ANALYSIS_NAME}" self.prepare = Prepare(self.lib_path) self.prepare.finished.connect(self.launch) def launch_mask(self): self._launch_begin() self._launch_mask() self.prepare.start() def _launch_mask(self): self.lib_path = "PartSeg._roi_mask.main_window" self.final_title = f"{APP_NAME} {MASK_NAME}" self.prepare = Prepare(self.lib_path) self.prepare.finished.connect(self.launch) def window_shown(self): self.close() def launch(self): if self.prepare.result is None: self.close() return if self.prepare.errors: errors_message = QMessageBox() errors_message.setText("There are errors during start") errors_message.setInformativeText( "During load saved state some of data could not be load properly\n" "The files has prepared backup copies in state directory (Help > State directory)" ) errors_message.setStandardButtons(QMessageBox.Ok) text = "\n".join("File: " + x[0] + "\n" + str(x[1]) for x in self.prepare.errors) errors_message.setDetailedText(text) errors_message.exec_() wind = self.prepare.result(title=self.final_title, signal_fun=self.window_shown) wind.show() self.wind = wind
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 MainDialog(DialogBase): """Main dialog for the anaconda navgator updater.""" # Signals sig_application_updated = Signal() sig_ready = Signal() # Class variables PACKAGE = 'anaconda-navigator' WIDTH = 450 HEIGHT = 200 def __init__(self, latest_version=None, prefix=None): """Main dialog for the anaconda navgator updater.""" super(MainDialog, self).__init__() # Variables self.api = CondaAPI() self.prefix = prefix or os.environ.get('CONDA_PREFIX', self.api.ROOT_PREFIX) self.info = {} self.first_run = True self.setup_ready = False self.busy = False self.up_to_date = False self.error = False self.success = False self.status = '' self.current_version = None self.latest_version = latest_version self.style_sheet = load_style_sheet() self.timer = QTimer() self.timer_2 = QTimer() self._windows_appusermodelid = None # Widgets self.message_box = None # For testing self.label_icon = QSvgWidget() self.label_message = LabelBase( "There's a new version of Anaconda Navigator available. " "We strongly recommend you to update.") self.label_status = LabelBase('') self.progress_bar = QProgressBar() self.button_cancel = ButtonNormal('Dismiss') self.button_update = ButtonPrimary('Update now') self.button_launch = ButtonPrimary('Launch Navigator') # Widgets setup if WIN: self._windows_appusermodelid = set_windows_appusermodelid() self.setMinimumSize(self.WIDTH, self.HEIGHT) self.label_message.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.label_message.setWordWrap(True) self.label_status.setWordWrap(True) self.button_update.setAutoDefault(True) self.button_launch.setAutoDefault(True) self.button_cancel.setFocusPolicy(Qt.NoFocus) self.timer.setInterval(1000) self.timer_2.setInterval(5000) self.progress_bar.setTextVisible(False) self.progress_bar.setStyleSheet(self.style_sheet) self.label_icon.load(images.ANACONDA_LOGO) self.label_icon.setMaximumSize(QSize(64, 64)) self.label_icon.setMinimumSize(QSize(64, 64)) self.setWindowTitle('Anaconda Navigator Updater') self.progress_bar.setMaximumWidth(self.WIDTH / 3) self.setMinimumWidth(self.WIDTH) self.setMaximumWidth(self.WIDTH) self.setMinimumHeight(self.HEIGHT) # Layouts layout_status = QHBoxLayout() layout_status.addWidget(self.label_status) layout_status.addWidget(SpacerHorizontal()) layout_status.addWidget(self.progress_bar) layout_text = QVBoxLayout() layout_text.addWidget(self.label_message) layout_text.addStretch() layout_text.addWidget(SpacerVertical()) layout_text.addLayout(layout_status) layout_icon = QVBoxLayout() layout_icon.addWidget(self.label_icon) layout_icon.addStretch() layout_top = QHBoxLayout() layout_top.addLayout(layout_icon) layout_top.addWidget(SpacerHorizontal()) layout_top.addLayout(layout_text) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_update) layout_buttons.addWidget(self.button_launch) layout = QVBoxLayout() layout.addLayout(layout_top) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addStretch() layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_update.clicked.connect(self.install_update) self.button_cancel.clicked.connect(self.reject) self.button_launch.clicked.connect(self.launch) self.timer.timeout.connect(self.refresh) self.timer_2.timeout.connect(self.check_conditions) # Setup self.timer.start() self.timer_2.start() self.check_conditions() self.refresh() def check_conditions(self): """Check every 5 seconds installed packages in case codna was used.""" packages = self.api.linked(prefix=self.prefix) package = [p for p in packages if self.PACKAGE in p] if package: n, v, b = self.api.split_canonical_name(package[0]) self.current_version = v else: self.current_version = None if self.latest_version is None: worker_search = self.api.search(self.PACKAGE, platform=self.api.get_platform()) worker_search.sig_finished.connect(self._search_callback) else: worker = self.api.info() worker.sig_finished.connect(self.setup) self.check_versions() def check_versions(self): """Check if navigator is up to date.""" if self.latest_version and self.current_version: from distutils.version import LooseVersion cur_ver = LooseVersion(self.current_version) lat_ver = LooseVersion(self.latest_version) self.up_to_date = cur_ver >= lat_ver else: self.up_to_date = False def _search_callback(self, worker, output, error): """Setup the widget.""" if isinstance(output, dict): packages = output.get(self.PACKAGE, []) versions = [package.get('version') for package in packages] unique_versions = [] for version in versions: if version not in unique_versions: unique_versions.append(version) if unique_versions: self.latest_version = unique_versions[-1] self.check_versions() worker = self.api.info() worker.sig_finished.connect(self.setup) self.refresh() def setup(self, worker, info, error): """Setup the widget.""" self.info = info self.setup_ready = True self.sig_ready.emit() self.refresh() if self.button_update.isVisible(): self.button_update.setFocus() if self.button_launch.isVisible(): self.button_launch.setFocus() def update_style_sheet(self): """Update custom CSS style sheet.""" self.style_sheet = load_style_sheet() self.setStyleSheet(self.style_sheet) def refresh(self): """Refresh enabled/disabled status of widgets.""" current_version = 'Not installed' if self.current_version: current_version = self.current_version latest_version = '-' if self.latest_version: latest_version = self.latest_version main_message = ( "Current version: <i>{0}</i><br>" "Available version: <b>{1}</b><br>").format( current_version, latest_version) message = self.status running = self.check_running() self.button_launch.setVisible(False) if not self.setup_ready: self.button_update.setDisabled(True) self.progress_bar.setVisible(True) message = 'Updating index...' self.update_status(message) elif self.busy: self.button_update.setDisabled(True) self.progress_bar.setVisible(True) else: self.progress_bar.setVisible(False) if running: message = 'Please close Anaconda Navigator before updating.' self.button_update.setDisabled(running) elif not running: self.button_update.setDisabled(False) if self.success and self.current_version: message = 'Anaconda Navigator was updated successfully.' self.button_update.setVisible(False) self.button_launch.setVisible(True) elif self.up_to_date: message = 'Anaconda Navigator is already up to date.' self.button_update.setVisible(False) self.button_launch.setVisible(True) elif not self.error: self.button_update.setVisible(True) if self.current_version: message = ('An update for Anaconda Navigator is now ' 'available.') self.button_update.setText('Update now') else: message = ( 'Anaconda Navigator is available for install.') self.button_update.setText('Install now') if self.error: self.button_update.setDisabled(False) message = 'Cannot update Anaconda Navigator, <b>{0}</b>' message = message.format(self.error) self.label_status.setText(message) self.label_message.setText(main_message) def update_status(self, status='', value=-1, max_val=-1): """Update progress bar and message status.""" if status: self.status = status self.label_status.setText(status) if value < 0 and max_val < 0: self.progress_bar.setRange(0, 0) else: self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(max_val) self.progress_bar.setValue(value) def check_running(self): """Check if Anaconda Navigator is running.""" # Create file lock lock = filelock.FileLock(NAVIGATOR_LOCKFILE) try: running = False with lock.acquire(timeout=0.01): pass except filelock.Timeout: running = True return running # --- Conda actions and helpers # ------------------------------------------------------------------------- def partial_output_ready(self, worker, output, error): """Handle conda partial output ready.""" self.busy = True # print(type(output)) # print(output) # Get errors and data from ouput if it exists fetch = None if output and isinstance(output, dict): fetch = output.get('fetch') max_val = output.get('maxval', -1) value = output.get('progress', -1) if fetch: status = 'Fetching <b>{0}</b>...'.format(fetch) self.update_status(status=status, max_val=max_val, value=value) def output_ready(self, worker, output, error): """Handle conda output ready.""" self.check_conditions() # Get errors and data from ouput if it exists error_text = output.get('error', '') exception_type = output.get('exception_type', '') exception_name = output.get('exception_name', '') success = output.get('success') actions = output.get('actions', {}) # op_order = output.get('op_order', []) # action_check_fetch = actions.get('CHECK_FETCH', []) # action_rm_fetch = actions.get('RM_FETCHED', []) # action_fetch = actions.get('FETCH', []) # action_check_extract = actions.get('CHECK_EXTRACT', []) # action_rm_extract = actions.get('RM_EXTRACTED', []) # action_extract = actions.get('EXTRACT', []) # action_unlink = actions.get('UNLINK', []) action_link = actions.get('LINK', []) # action_symlink_conda = actions.get('SYMLINK_CONDA', []) self.busy = False # Get errors from json output if error_text or exception_type or exception_name or not success: self.error = exception_name self.success = False self.up_to_date = False elif success and action_link: self.sig_application_updated.emit() self.error = None self.success = True self.up_to_date = False elif success: self.success = False self.error = None self.up_to_date = True worker.lock.release() self.refresh() def install_update(self): """Install the specified version or latest version of navigator.""" self.busy = True self.refresh() # conda_prefix = self.info.et('conda_prefix') # root_prefix = self.info.et('root_prefix') navigator_prefixes = [ # os.path.join(self.api.ROOT_PREFIX, 'envs', '_navigator_'), # os.path.join(self.api.ROOT_PREFIX, 'envs', '_conda_'), self.prefix, ] for prefix in navigator_prefixes: if self.api.environment_exists(prefix=prefix): break if self.latest_version: pkgs = ['{0}=={1}'.format(self.PACKAGE, self.latest_version)] else: pkgs = [self.PACKAGE.format(self.latest_version)] # Lock Navigator lock = filelock.FileLock(NAVIGATOR_LOCKFILE) lock.acquire() worker = self.api.install(prefix=prefix, pkgs=pkgs) worker.lock = lock worker.sig_partial.connect(self.partial_output_ready) worker.sig_finished.connect(self.output_ready) self.refresh() if self.prefix == self.api.ROOT_PREFIX: name = 'root' else: name = os.path.basename(self.prefix) self.button_launch.setFocus() if self.current_version: msg = 'Updating package on <b>{0}</b>...'.format(name) else: msg = 'Installing package on <b>{0}</b>...'.format(name) self.update_status(msg) def launch(self): """Launch Anaconda Navigator.""" leave_path_alone = True root_prefix = self.api.ROOT_PREFIX prefix = self.prefix command = ['anaconda-navigator'] # Use the app bundle on OSX if MAC: command = ['open', os.path.join(prefix, 'Anaconda-Navigator.app')] launch_cmd( prefix, command, leave_path_alone, package_name='anaconda-navigator-app', root_prefix=root_prefix, ) self.close() # --- Qt Overrides # ------------------------------------------------------------------------- def reject(self): """Override Qt method.""" if self.busy: msg_box = MessageBoxQuestion(title='Quit Navigator Updater?', text='Anaconda Navigator is being ' 'updated. <br><br>' 'Are you sure you want to quit?') if msg_box.exec_(): super(MainDialog, self).reject() else: super(MainDialog, self).reject()
class PackagesDialog(DialogBase): """Package dependencies dialog.""" sig_setup_ready = Signal() def __init__( self, parent=None, packages=None, pip_packages=None, remove_only=False, update_only=False, ): """About dialog.""" super(PackagesDialog, self).__init__(parent=parent) # Variables self.api = AnacondaAPI() self.actions = None self.packages = packages or [] self.pip_packages = pip_packages or [] # Widgets self.stack = QStackedWidget() self.table = QTableWidget() self.text = QTextEdit() self.label_description = LabelBase() self.label_status = LabelBase() self.progress_bar = QProgressBar() self.button_ok = ButtonPrimary('Apply') self.button_cancel = ButtonNormal('Cancel') # Widget setup self.text.setReadOnly(True) self.stack.addWidget(self.table) self.stack.addWidget(self.text) if remove_only: text = 'The following packages will be removed:<br>' else: text = 'The following packages will be modified:<br>' self.label_description.setText(text) self.label_description.setWordWrap(True) self.label_description.setWordWrap(True) self.label_status.setWordWrap(True) self.table.horizontalScrollBar().setVisible(False) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setAlternatingRowColors(True) self.table.setSelectionMode(QAbstractItemView.NoSelection) self.table.setSortingEnabled(True) self._hheader = self.table.horizontalHeader() self._vheader = self.table.verticalHeader() self._hheader.setStretchLastSection(True) self._hheader.setDefaultAlignment(Qt.AlignLeft) self._hheader.setSectionResizeMode(self._hheader.Fixed) self._vheader.setSectionResizeMode(self._vheader.Fixed) self.button_ok.setMinimumWidth(70) self.button_ok.setDefault(True) self.base_minimum_width = 300 if remove_only else 420 if remove_only: self.setWindowTitle("Remove Packages") elif update_only: self.setWindowTitle("Update Packages") else: self.setWindowTitle("Install Packages") self.setMinimumWidth(self.base_minimum_width) # Layouts layout_progress = QHBoxLayout() layout_progress.addWidget(self.label_status) layout_progress.addWidget(SpacerHorizontal()) layout_progress.addWidget(self.progress_bar) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout = QVBoxLayout() layout.addWidget(self.label_description) layout.addWidget(SpacerVertical()) layout.addWidget(self.stack) layout.addWidget(SpacerVertical()) layout.addLayout(layout_progress) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_ok.setDisabled(True) # Setup self.table.setDisabled(True) self.update_status('Solving package specifications', value=0, max_value=0) def setup(self, worker, output, error): """Setup the widget to include the list of dependencies.""" if not isinstance(output, dict): output = {} packages = sorted(pkg.split('==')[0] for pkg in self.packages) success = output.get('success') error = output.get('error', '') exception_name = output.get('exception_name', '') actions = output.get('actions', []) prefix = worker.prefix if exception_name: message = exception_name else: # All requested packages already installed message = output.get('message', ' ') navi_deps_error = self.api.check_navigator_dependencies( actions, prefix) description = self.label_description.text() if error: description = 'No packages will be modified.' self.stack.setCurrentIndex(1) self.button_ok.setDisabled(True) if self.api.is_offline(): error = ("Some of the functionality of Anaconda Navigator " "will be limited in <b>offline mode</b>. <br><br>" "Installation and upgrade actions will be subject to " "the packages currently available on your package " "cache.") self.text.setText(error) elif navi_deps_error: description = 'No packages will be modified.' error = ('Downgrading/removing these packages will modify ' 'Anaconda Navigator dependencies.') self.text.setText(error) self.stack.setCurrentIndex(1) message = 'NavigatorDependenciesError' self.button_ok.setDisabled(True) elif success and actions: self.stack.setCurrentIndex(0) # Conda 4.3.x if isinstance(actions, list): actions_link = actions[0].get('LINK', []) actions_unlink = actions[0].get('UNLINK', []) # Conda 4.4.x else: actions_link = actions.get('LINK', []) actions_unlink = actions.get('UNLINK', []) deps = set() deps = deps.union({p['name'] for p in actions_link}) deps = deps.union({p['name'] for p in actions_unlink}) deps = deps - set(packages) deps = sorted(list(deps)) count_total_packages = len(packages) + len(deps) plural_total = 's' if count_total_packages != 1 else '' plural_selected = 's' if len(packages) != 1 else '' self.table.setRowCount(count_total_packages) self.table.setColumnCount(4) if actions_link: description = '{0} package{1} will be installed'.format( count_total_packages, plural_total) self.table.showColumn(2) self.table.showColumn(3) elif actions_unlink and not actions_link: self.table.hideColumn(2) self.table.hideColumn(3) self.table.setHorizontalHeaderLabels( ['Name', 'Unlink', 'Link', 'Channel']) description = '{0} package{1} will be removed'.format( count_total_packages, plural_total) for row, pkg in enumerate(packages + deps): link_item = [p for p in actions_link if p['name'] == pkg] if not link_item: link_item = { 'version': '-'.center(len('link')), 'channel': '-'.center(len('channel')), } else: link_item = link_item[0] unlink_item = [p for p in actions_unlink if p['name'] == pkg] if not unlink_item: unlink_item = { 'version': '-'.center(len('link')), } else: unlink_item = unlink_item[0] unlink_version = str(unlink_item['version']) link_version = str(link_item['version']) item_unlink_v = QTableWidgetItem(unlink_version) item_link_v = QTableWidgetItem(link_version) item_link_c = QTableWidgetItem(link_item['channel']) if pkg in packages: item_name = QTableWidgetItem(pkg) else: item_name = QTableWidgetItem('*' + pkg) items = [item_name, item_unlink_v, item_link_v, item_link_c] for column, item in enumerate(items): item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.table.setItem(row, column, item) if deps: message = ( '<b>*</b> indicates the package is a dependency of a ' 'selected package{0}<br>').format(plural_selected) self.button_ok.setEnabled(True) self.table.resizeColumnsToContents() unlink_width = self.table.columnWidth(1) if unlink_width < 60: self.table.setColumnWidth(1, 60) self.table.setHorizontalHeaderLabels( ['Name ', 'Unlink ', 'Link ', 'Channel ']) self.table.setEnabled(True) self.update_status(message=message) self.label_description.setText(description) # Adjust size after data has populated the table self.table.resizeColumnsToContents() width = sum( self.table.columnWidth(i) for i in range(self.table.columnCount())) delta = (self.width() - self.table.width() + self.table.verticalHeader().width() + 10) new_width = width + delta if new_width < self.base_minimum_width: new_width = self.base_minimum_width self.setMinimumWidth(new_width) self.setMaximumWidth(new_width) self.sig_setup_ready.emit() def update_status(self, message='', value=None, max_value=None): """Update status of packages dialog.""" self.label_status.setText(message) if max_value is None and value is None: self.progress_bar.setVisible(False) else: self.progress_bar.setVisible(True) self.progress_bar.setMaximum(max_value) self.progress_bar.setValue(value)
class BatchProcessingView(View): btn_run_batch: QPushButton progress_bar: QProgressBar field_channel: QLineEdit field_workflow_config: FileInput field_input_dir: FileInput field_output_dir: FileInput def __init__(self, controller: IBatchProcessingController): super().__init__(template_class=MainTemplate) if controller is None: raise ValueError("controller") self._controller = controller self.setObjectName("batchProcessingView") def load(self, model=None): self._setup_ui() def _setup_ui(self): """ Set up the UI for the BatchProcessingView """ layout = QVBoxLayout() self.setLayout(layout) # Workflow config self.field_workflow_config = FileInput( mode=FileInputMode.FILE, filter="Json file (*.json)", placeholder_text="Load a JSON workflow file..." ) self.field_workflow_config.file_selected.connect(self._form_field_changed) row1 = FormRow("1. Load workflow:", self.field_workflow_config) # Channel index self.field_channel = QLineEdit("0") self.field_channel.setValidator(QIntValidator(bottom=0)) self.field_channel.textChanged.connect(self._form_field_changed) row2 = FormRow("2. Structure channel index:", self.field_channel) # Input dir self.field_input_dir = FileInput(mode=FileInputMode.DIRECTORY, placeholder_text="Select a directory...") self.field_input_dir.file_selected.connect(self._form_field_changed) row3 = FormRow("3. Input directory:", self.field_input_dir) # Output dir self.field_output_dir = FileInput(mode=FileInputMode.DIRECTORY, placeholder_text="Select a directory...") self.field_output_dir.file_selected.connect(self._form_field_changed) row4 = FormRow("4. Output directory:", self.field_output_dir) # Help label = QLabel() label.setText("Supported file formats: .tif, .tiff, .czi, .ome.tif, .ome.tiff") form = QWidget() form.setLayout(Form([row1, row2, row3, row4])) layout.addWidget(form) layout.addWidget(label) # Submit self.btn_run_batch = QPushButton("Run Batch") self.btn_run_batch.clicked.connect(self._btn_run_batch_clicked) self.update_button(enabled=False) layout.addWidget(self.btn_run_batch) # Progress bar self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) self.progress_bar.setValue(0) self.progress_bar.setTextVisible(True) self.progress_bar.setVisible(False) layout.addWidget(self.progress_bar) def update_button(self, enabled: bool): """ Update state of process button Inputs: enabled: True to enable the button, false to disable it """ self.btn_run_batch.setEnabled(enabled) def set_run_batch_in_progress(self): """ Update page to reflect that a batch run is in progress """ # TODO make a CancelButton widget to avoid repeating this connect / disconnect pattern self.btn_run_batch.setText("Cancel") self.btn_run_batch.clicked.disconnect() self.btn_run_batch.clicked.connect(self._btn_run_batch_cancel_clicked) self.progress_bar.setVisible(True) def reset_run_batch(self): """ Reset page state to reflect that there is no batch run in progress """ self.progress_bar.setValue(0) self.btn_run_batch.setText("Run Batch") self.btn_run_batch.clicked.disconnect() self.btn_run_batch.clicked.connect(self._btn_run_batch_clicked) self.progress_bar.setVisible(False) def set_progress(self, progress: int): """ Update progress bar Inputs: progress (int): numerical value to set the progress bar to """ self.progress_bar.setValue(progress) ##################################################################### # Event handlers ##################################################################### def _btn_run_batch_clicked(self): self._controller.run_batch() def _btn_run_batch_cancel_clicked(self): self.btn_run_batch.setText("Canceling...") self._controller.cancel_run_batch() def _form_field_changed(self, value): workflow_config = self.field_workflow_config.selected_file channel_index = int(self.field_channel.text()) if self.field_channel.text() else None input_dir = self.field_input_dir.selected_file output_dir = self.field_output_dir.selected_file self._controller.update_batch_parameters(workflow_config, channel_index, input_dir, output_dir)
class AlgorithmOptions(QWidget): def __init__(self, settings: StackSettings, image_view: StackImageView): super().__init__() self.settings = settings self.view_name = image_view.name self.show_result = QEnumComboBox( enum_class=LabelEnum) # QCheckBox("Show result") self._set_show_label_from_settings() self.opacity = QDoubleSpinBox() self.opacity.setRange(0, 1) self.opacity.setSingleStep(0.1) self._set_opacity_from_settings() self.only_borders = QCheckBox("Only borders") self._set_border_mode_from_settings() self.borders_thick = QSpinBox() self.borders_thick.setRange(1, 25) self.borders_thick.setSingleStep(1) self._set_border_thick_from_settings() self.execute_in_background_btn = QPushButton("Execute in background") self.execute_in_background_btn.setToolTip( "Run calculation in background. Put result in multiple files panel" ) self.execute_btn = QPushButton("Execute") self.execute_btn.setStyleSheet("QPushButton{font-weight: bold;}") self.execute_all_btn = QPushButton("Execute all") self.execute_all_btn.setToolTip( "Execute in batch mode segmentation with current parameter. File list need to be specified in image tab." ) self.execute_all_btn.setDisabled(True) self.save_parameters_btn = QPushButton("Save parameters") self.block_execute_all_btn = False self.algorithm_choose_widget = AlgorithmChoose(settings, mask_algorithm_dict) self.algorithm_choose_widget.result.connect(self.execution_result_set) self.algorithm_choose_widget.finished.connect(self.execution_finished) self.algorithm_choose_widget.progress_signal.connect( self.progress_info) # self.stack_layout = QStackedLayout() self.keep_chosen_components_chk = QCheckBox("Save selected components") self.keep_chosen_components_chk.setToolTip( "Save chosen components when loading segmentation form file\n or from multiple file widget." ) self.keep_chosen_components_chk.stateChanged.connect( self.set_keep_chosen_components) self.keep_chosen_components_chk.setChecked( settings.keep_chosen_components) self.show_parameters = QPushButton("Show parameters") self.show_parameters.setToolTip( "Show parameters of segmentation for each components") self.show_parameters_widget = SegmentationInfoDialog( self.settings, self.algorithm_choose_widget.change_algorithm) self.show_parameters.clicked.connect(self.show_parameters_widget.show) self.choose_components = ChosenComponents() self.choose_components.check_change_signal.connect( image_view.refresh_selected) self.choose_components.mouse_leave.connect(image_view.component_unmark) self.choose_components.mouse_enter.connect(image_view.component_mark) # WARNING works only with one channels algorithms # SynchronizeValues.add_synchronization("channels_chose", widgets_list) self.chosen_list = [] self.progress_bar2 = QProgressBar() self.progress_bar2.setHidden(True) self.progress_bar = QProgressBar() self.progress_bar.setHidden(True) self.progress_info_lab = QLabel() self.progress_info_lab.setHidden(True) self.file_list = [] self.batch_process = BatchProceed() self.batch_process.progress_signal.connect(self.progress_info) self.batch_process.error_signal.connect(self.execution_all_error) self.batch_process.execution_done.connect(self.execution_all_done) self.batch_process.range_signal.connect(self.progress_bar.setRange) self.is_batch_process = False self.setContentsMargins(0, 0, 0, 0) main_layout = QVBoxLayout() # main_layout.setSpacing(0) opt_layout = QHBoxLayout() opt_layout.setContentsMargins(0, 0, 0, 0) opt_layout.addWidget(self.show_result) opt_layout.addWidget(right_label("Opacity:")) opt_layout.addWidget(self.opacity) main_layout.addLayout(opt_layout) opt_layout2 = QHBoxLayout() opt_layout2.setContentsMargins(0, 0, 0, 0) opt_layout2.addWidget(self.only_borders) opt_layout2.addWidget(right_label("Border thick:")) opt_layout2.addWidget(self.borders_thick) main_layout.addLayout(opt_layout2) btn_layout = QGridLayout() btn_layout.setContentsMargins(0, 0, 0, 0) btn_layout.addWidget(self.execute_btn, 0, 0) btn_layout.addWidget(self.execute_in_background_btn, 0, 1) btn_layout.addWidget(self.execute_all_btn, 1, 0) btn_layout.addWidget(self.save_parameters_btn, 1, 1) main_layout.addLayout(btn_layout) main_layout.addWidget(self.progress_bar2) main_layout.addWidget(self.progress_bar) main_layout.addWidget(self.progress_info_lab) main_layout.addWidget(self.algorithm_choose_widget, 1) # main_layout.addWidget(self.algorithm_choose) # main_layout.addLayout(self.stack_layout, 1) main_layout.addWidget(self.choose_components) down_layout = QHBoxLayout() down_layout.addWidget(self.keep_chosen_components_chk) down_layout.addWidget(self.show_parameters) main_layout.addLayout(down_layout) main_layout.addStretch() main_layout.setContentsMargins(0, 0, 0, 0) # main_layout.setSpacing(0) self.setLayout(main_layout) # noinspection PyUnresolvedReferences self.execute_in_background_btn.clicked.connect( self.execute_in_background) self.execute_btn.clicked.connect(self.execute_action) self.execute_all_btn.clicked.connect(self.execute_all_action) self.save_parameters_btn.clicked.connect(self.save_parameters) # noinspection PyUnresolvedReferences self.opacity.valueChanged.connect(self._set_opacity) # noinspection PyUnresolvedReferences self.show_result.currentEnumChanged.connect(self._set_show_label) self.only_borders.stateChanged.connect(self._set_border_mode) # noinspection PyUnresolvedReferences self.borders_thick.valueChanged.connect(self._set_border_thick) image_view.component_clicked.connect( self.choose_components.other_component_choose) settings.chosen_components_widget = self.choose_components settings.components_change_list.connect( self.choose_components.new_choose) settings.image_changed.connect( self.choose_components.remove_components) settings.connect_to_profile( f"{self.view_name}.image_state.only_border", self._set_border_mode_from_settings) settings.connect_to_profile( f"{self.view_name}.image_state.border_thick", self._set_border_thick_from_settings) settings.connect_to_profile(f"{self.view_name}.image_state.opacity", self._set_opacity_from_settings) settings.connect_to_profile(f"{self.view_name}.image_state.show_label", self._set_show_label_from_settings) def _set_border_mode(self, value: bool): self.settings.set_in_profile( f"{self.view_name}.image_state.only_border", value) def _set_border_thick(self, value: int): self.settings.set_in_profile( f"{self.view_name}.image_state.border_thick", value) def _set_opacity(self, value: float): self.settings.set_in_profile(f"{self.view_name}.image_state.opacity", value) def _set_show_label(self, value: LabelEnum): self.settings.set_in_profile( f"{self.view_name}.image_state.show_label", value) def _set_border_mode_from_settings(self): self.only_borders.setChecked( self.settings.get_from_profile( f"{self.view_name}.image_state.only_border", True)) def _set_border_thick_from_settings(self): self.borders_thick.setValue( self.settings.get_from_profile( f"{self.view_name}.image_state.border_thick", 1)) def _set_opacity_from_settings(self): self.opacity.setValue( self.settings.get_from_profile( f"{self.view_name}.image_state.opacity", 1.0)) def _set_show_label_from_settings(self): self.show_result.setCurrentEnum( self.settings.get_from_profile( f"{self.view_name}.image_state.show_label", LabelEnum.Show_results)) @Slot(int) def set_keep_chosen_components(self, val): self.settings.set_keep_chosen_components(val) def save_parameters(self): dial = PSaveDialog(io_functions.save_parameters_dict, system_widget=False, settings=self.settings, path=IO_SAVE_DIRECTORY) if not dial.exec_(): return res = dial.get_result() res.save_class.save(res.save_destination, self.algorithm_choose_widget.current_parameters()) def file_list_change(self, val): self.file_list = val if len(self.file_list) > 0 and not self.block_execute_all_btn: self.execute_all_btn.setEnabled(True) else: self.execute_all_btn.setDisabled(True) def get_chosen_components(self): return sorted(self.choose_components.get_chosen()) @property def segmentation(self): return self.settings.roi @segmentation.setter def segmentation(self, val): self.settings.roi = val def _image_changed(self): self.settings.roi = None self.choose_components.set_chose([], []) def _execute_in_background_init(self): if self.batch_process.isRunning(): return self.progress_bar2.setVisible(True) self.progress_bar2.setRange(0, self.batch_process.queue.qsize()) self.progress_bar2.setValue(self.batch_process.index) self.progress_bar.setVisible(True) self.progress_bar.setValue(0) self.execute_btn.setDisabled(True) self.batch_process.start() def execute_in_background(self): # TODO check if components are properly passed widget = self.algorithm_choose_widget.current_widget() segmentation_profile = widget.get_segmentation_profile() task = BatchTask(self.settings.get_project_info(), segmentation_profile, None) self.batch_process.add_task(task) self.progress_bar2.setRange(0, self.progress_bar2.maximum() + 1) self._execute_in_background_init() def execute_all_action(self): dial = PSaveDialog( SaveROI, settings=self.settings, system_widget=False, path="io.save_batch", file_mode=PSaveDialog.Directory, ) if not dial.exec_(): return folder_path = str(dial.selectedFiles()[0]) widget = self.algorithm_choose_widget.current_widget() save_parameters = dial.values segmentation_profile = widget.get_segmentation_profile() for file_path in self.file_list: task = BatchTask(file_path, segmentation_profile, (folder_path, save_parameters)) self.batch_process.add_task(task) self.progress_bar2.setRange( 0, self.progress_bar2.maximum() + len(self.file_list)) self._execute_in_background_init() def execution_all_error(self, text): QMessageBox.warning(self, "Proceed error", text) def execution_all_done(self): if not self.batch_process.queue.empty(): self._execute_in_background_init() return self.execute_btn.setEnabled(True) self.block_execute_all_btn = False if len(self.file_list) > 0: self.execute_all_btn.setEnabled(True) self.progress_bar.setHidden(True) self.progress_bar2.setHidden(True) self.progress_info_lab.setHidden(True) def execute_action(self): self.execute_btn.setDisabled(True) self.execute_all_btn.setDisabled(True) self.block_execute_all_btn = True self.is_batch_process = False self.progress_bar.setRange(0, 0) self.choose_components.setDisabled(True) chosen = sorted(self.choose_components.get_chosen()) blank = get_mask(self.settings.roi, self.settings.mask, chosen) if blank is not None: # Problem with handling time data in algorithms # TODO Fix This blank = blank[0] self.progress_bar.setHidden(False) widget: AlgorithmSettingsWidget = self.algorithm_choose_widget.current_widget( ) widget.set_mask(blank) self.progress_bar.setRange(0, widget.algorithm.get_steps_num()) widget.execute() self.chosen_list = chosen def progress_info(self, text, num, file_name="", file_num=0): self.progress_info_lab.setVisible(True) if file_name != "": self.progress_info_lab.setText(file_name + "\n" + text) else: self.progress_info_lab.setText(text) self.progress_bar.setValue(num) self.progress_bar2.setValue(file_num) def execution_finished(self): self.execute_btn.setEnabled(True) self.block_execute_all_btn = False if len(self.file_list) > 0: self.execute_all_btn.setEnabled(True) self.progress_bar.setHidden(True) self.progress_info_lab.setHidden(True) self.choose_components.setDisabled(False) def execution_result_set(self, result): self.settings.set_segmentation_result(result) def showEvent(self, _): widget = self.algorithm_choose_widget.current_widget() widget.image_changed(self.settings.image)