示例#1
0
class ProjectsTab(WidgetBase):
    """
    Projects management tab.
    """
    #    sig_apps_changed = Signal(str)
    sig_apps_updated = Signal()
    sig_project_updated = Signal()
    sig_status_updated = Signal(str, int, int, int)

    def __init__(self, parent=None):
        super(ProjectsTab, self).__init__(parent)

        self.api = AnacondaAPI()
        self.current_project_item = None
        self.dev_tool_visible = False
        self.new_project_name_template = "New project {0}"
        self.project_counter = 1  # Counter for new projects
        self.projects_dict = {}

        # Widgets
        self.button_add = QPushButton("Import")
        self.button_create = QPushButton("Create")
        self.button_delete = QPushButton("Delete")
        self.button_duplicate = QPushButton("Duplicate")
        self.label_search = QLabel('')
        self.list_projects = ProjectsListWidget()
        self.project_widget = ProjectWidget(parent=self)
        self.text_search = QLineEdit()
        search_icon = qta.icon('fa.search')

        # Widgets setup
        self.label_search.setPixmap(search_icon.pixmap(16, 16))
        self.text_search.setPlaceholderText("Search Projects")
        self.setObjectName("Tab")
        self.list_projects.setObjectName("ListProjects")

        # Layouts
        projects_layout = QVBoxLayout()

        search_layout = QHBoxLayout()
        search_layout.addWidget(self.text_search)
        search_layout.addWidget(self.label_search)

        projects_layout.addLayout(search_layout)
        projects_layout.addWidget(self.list_projects)

        project_buttons_layout = QHBoxLayout()
        project_buttons_layout.addWidget(self.button_create)
        project_buttons_layout.addWidget(self.button_add)
        project_buttons_layout.addWidget(self.button_duplicate)
        project_buttons_layout.addWidget(self.button_delete)

        projects_layout.addLayout(project_buttons_layout)

        main_layout = QHBoxLayout()
        main_layout.addSpacing(20)
        main_layout.addLayout(projects_layout, 1)
        main_layout.addSpacing(20)
        main_layout.addWidget(self.project_widget, 2)

        self.setLayout(main_layout)

        # Signals
        self.button_add.clicked.connect(lambda: self.add_project(path=None))
        self.button_create.clicked.connect(
            lambda: self.create_new_project(path=None))
        self.button_delete.clicked.connect(self.delete_project)
        self.button_duplicate.clicked.connect(self.duplicate_project)

        self.list_projects.itemChanged.connect(self.set_app)
        self.list_projects.itemSelectionChanged.connect(
            self.load_project_information)

        self.project_widget.sig_apps_updated.connect(self.sig_apps_updated)
        self.project_widget.sig_project_updated.connect(
            self.sig_project_updated)
        self.project_widget.sig_project_name_updated.connect(
            self.update_project_name)
        self.project_widget.sig_project_icon_updated.connect(
            self.update_project_icon)
        self.project_widget.sig_project_commands_updated.connect(
            self.update_project_commands)

        self.text_search.textChanged.connect(self.filter_projects)

        # Setup
        self.button_duplicate.setDisabled(True)
        self.button_duplicate.setVisible(False)
        self.button_delete.setDisabled(True)
        self.update_style_sheet()

    def update_style_sheet(self, style_sheet=None):
        """
        """
        if style_sheet is None:
            style_sheet = load_style_sheet()

        self.list_projects.setFrameStyle(QFrame.NoFrame)
        self.list_projects.setFrameShape(QFrame.NoFrame)
        self.list_projects.setStyleSheet(style_sheet)
        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            item.setSizeHint(QSize(60, 60))
        self.list_projects.repaint()

    def setup_tab(self):
        """
            self.update_visibility(disabled=True)

        """
        self.load_projects()
        self.project_widget.setDisabled(True)
        self.toogle_dev_tools(visible=self.dev_tool_visible)
        self.load_last_active_project()

    # --- Helpers
    # -------------------------------------------------------------------------
    def _update_project_numbering(self):
        """
        Update the new project count number based on existing project names.
        """
        new_project_numbering = [0]
        for path in self.projects_dict:
            project = self.projects_dict[path]

            if self.new_project_name_template[:-3] in project.name:
                try:
                    number = int(project.name.split(' ')[-1])
                except Exception:
                    number = 0
                new_project_numbering.append(number)

        self.project_counter = max(new_project_numbering) + 1

    def set_app(self, item):
        """
        Check box changed, set project's is_app property.
        """
        item.project.is_app = item.checkState() == Qt.Checked
        item.project.save(item.path)
        self.sig_apps_updated.emit()

    def filter_projects(self):
        """
        Filter displayed projects by matching search text.
        """
        text = self.text_search.text().lower()
        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            item.setHidden(text not in item.text().lower())
        self.toogle_dev_tools(visible=self.dev_tool_visible)

    def load_projects(self):
        """
        Load all projects available in the configuration folder and settings.
        """
        paths = get_projects_path()
        self.projects_dict = self.api.load_projects(paths)
        self._update_project_numbering()
        self.load_project_list()

    def load_last_active_project(self):
        """
        Make sure project selection ("current") persists.
        """
        active_path = CONF.get('main', 'current_project_path',
                               DEFAULT_PROJECT_PATH)

        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            if item.path == active_path:
                self.list_projects.setCurrentItem(item)
                self.current_project_item = item
                self.load_project_information()

    def load_project_information(self):
        """
        Update the project widget after a project has been selected.
        """
        # Project Widget update
        item = self.list_projects.currentItem()
        details = item.project
        path = item.path
        self.project_widget.load_project_information(path)
        self.current_project_item = item
        CONF.set('main', 'current_project_path', item.path)

        self.button_delete.setDisabled(details.is_default)
        self.button_duplicate.setDisabled(details.is_default)
        self.project_widget.setDisabled(False)

    def load_project_list(self):
        """
        Populate the project selection list.
        """
        self.list_projects.clear()

        for path in self.projects_dict:
            project = self.projects_dict[path]
            name = project.name

            item = QListWidgetItem(name)

            if getattr(project, 'icon', None):
                icon = self.api.load_icon(path, project)
            else:
                icon = qta.icon('fa.cog')

            item.setIcon(icon)
            item.project = project
            item.path = path
            if project.commands:
                item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
                              | Qt.ItemIsEnabled)
                if project.is_app:
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)

            self.list_projects.addItem(item)
            self.toogle_dev_tools(visible=self.dev_tool_visible)

        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            item.setSizeHint(QSize(item.sizeHint().width(), self._height()))

    def _height(self):
        """
        Get the height for the row in the widget based on OS font metrics.
        """
        return self.fontMetrics().height() * 2

    def toogle_dev_tools(self, visible=None):
        """
        Display/Hide development tools project folders.
        """
        if visible is not None:
            self.dev_tool_visible = visible
        else:
            self.dev_tool_visible = not self.dev_tool_visible

        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            is_conda_app = item.project.is_conda_app
            if is_conda_app:
                item.setHidden(not self.dev_tool_visible)

    def update_visibility(self, disabled=True):
        """
        Enable/Disabled widgets during operations.
        """
        self.list_projects.setDisabled(disabled)
        self.button_add.setDisabled(disabled)
        self.button_create.setDisabled(disabled)
        self.button_delete.setDisabled(disabled)
        self.button_duplicate.setDisabled(disabled)
        self.project_widget.setDisabled(disabled)

    def update_status(self, message='', timeout=0):
        """
        Update project tab status.
        """
        self.sig_status_updated.emit(message, timeout, -1, -1)

    def update_project_name(self, path, old_name, new_name):
        """
        Update project name item only. Avoid reloading projects.
        """
        item = self.get_item_by_path(path)
        item.setText(new_name)

    def update_project_icon(self, path, old_image_path, new_image_path):
        """
        Update project item icon only. Avoid reloading projects.
        """
        self.api.save_icon(new_image_path, path)
        icon = QIcon(QPixmap(new_image_path))
        item = self.get_item_by_path(path)
        item.setIcon(icon)

    def get_item_by_path(self, path):
        """
        """
        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)
            if item.path == path:
                return item

    def update_project_commands(self, path, commands):
        """
        Update project checkbox icon only. Avoid reloading projects.
        """
        # FIXME: There should be a better way!
        item = self.get_item_by_path(path)
        item.project.commands = commands
        if len(commands) != 0:
            item.setData(Qt.CheckStateRole, item.checkState())
        else:
            item.setData(Qt.CheckStateRole, None)

    # --- Main project tab actions
    # -------------------------------------------------------------------------
    def add_project(self, path=None, new_path=None):
        """
        Add/import existing project given by `path` into `new_path`.
        """
        if path is None:
            path = getexistingdirectory(caption="Select origin project folder "
                                        "to import",
                                        basedir=get_home_dir())
        if path:
            self.update_status('Adding project at {0}'.format(path))
            self.update_visibility(disabled=True)
            update_pointer(Qt.BusyCursor)

            # shutil.copytree does not work if the destionation folder exists
            #            os.removedirs(new_path)
            #            shutil.copytree(path, new_path)
            worker = self.api.add_project(path)
            worker.path = path
            worker.sig_finished.connect(self._project_added)

    def create_new_project(self, path=None):
        """
        Create a new project. If `path` is provided no dialog is openned.
        """
        if path is None:
            path = getexistingdirectory(caption='Select new project folder '
                                        'location',
                                        basedir=get_home_dir())

        # Check that project.yaml does not exists, otherwise warn the user and
        # prompt for action?
        if path:
            name = self.new_project_name_template.format(self.project_counter)
            self.update_status('Creating project "{0}"'.format(name))
            worker = self.api.create_new_project(path=path, name=name)
            worker.sig_finished.connect(self._project_created)
            worker.path = path
            self.update_visibility(disabled=True)
            self.project_counter += 1
            update_pointer(Qt.BusyCursor)

    def delete_project(self):
        """
        Remove project from listing and optionally delete all project files.
        """
        project = self.list_projects.currentItem().project

        # The default project cant be deleted. Enforce extra security for that
        if project.is_default:
            return

        msgbox = MessageCheckBox.warning(
            self,
            "Delete project",
            "Do you really want to delete project <strong>{0}</strong>?"
            "".format(project.name),
            QMessageBox.Yes | QMessageBox.No,
            checkbox_text="Delete all project files")

        reply = msgbox.exec_()
        if reply == MessageCheckBox.Yes:
            self.sig_status_updated.emit(
                'Deleting project '
                '"{0}"'.format(project.name), 0, -1, -1)
            item = self.list_projects.takeItem(self.list_projects.currentRow())
            projects_path = list(set(CONF.get('main', 'projects_path', [])))
            projects_path = list(projects_path)
            projects_path.remove(item.path)
            CONF.set('main', 'projects_path', projects_path)

            if msgbox.is_checked():
                if os.path.isdir(item.path):
                    shutil.rmtree(item.path)

            # Select the default project after deletion of a project
            default_item = self.list_projects.item(0)
            self.list_projects.itemClicked.emit(default_item)
            self.sig_status_updated.emit('', 0, -1, -1)
            self.sig_apps_updated.emit()

    def duplicate_project(self, path=None):
        """
        Copy selected project directory into `path`.
        """
        source_path = self.list_projects.currentItem().path
        if path is None:
            path = getexistingdirectory(caption="Select folder destination "
                                        "for project duplication",
                                        basedir=get_home_dir())
        if path:
            shutil.copy2(source_path, path)

    # --- Callbacks
    # -------------------------------------------------------------------------
    def _project_created(self, worker, output, error):
        """
        Callback after worker has finished creating a new project.
        """
        self._update_project_config(path=worker.path)
        self.update_visibility(disabled=False)

        # Select the project after created
        for i in range(self.list_projects.count()):
            item = self.list_projects.item(i)

            if worker.path == item.path:
                self.list_projects.setCurrentRow(i)
                self.list_projects.itemClicked.emit(item)
        update_pointer()

    def _project_added(self, worker, output, error):
        """
        Callback after worker has finished adding project.
        """
        self._project_created(worker, output, error)

    def _update_project_config(self, path):
        """
        Add given path to the CONF list of known projects.
        """
        projects_path = list(set(CONF.get('main', 'projects_path', [])))
        projects_path = list(projects_path)
        projects_path.append(path)
        CONF.set('main', 'projects_path', projects_path)
        self.load_projects()
        self.update_status('')
示例#2
0
    def __init__(self, parent=None):
        super(HomeTab, self).__init__(parent)

        # Variables
        self._parent = parent
        self.api = AnacondaAPI()
        self.setObjectName('Tab')

        # Widgetsugh
        main_icon = QLabel()
        self.list_applications = ListWidgetApplication()
        self.apps_model = None
        self.apps = None
        self.app_timers = None
        self.button_refresh = ButtonHomeRefresh('Refresh')
        self.frame_home = FrameHome(self)
        self.frame_home_top = FrameHomeTop(self)
        self.frame_home_bottom = FrameHomeTop(self)
        self.label_home = LabelHome('')
        self.label_status = QLabel('')
        self.timer_refresh = QTimer()

        # Widget setup
        pixmap = QPixmap(images.ANACONDA_ICON_128_PATH)
        main_icon.setPixmap(pixmap)
        main_text = ('My Applications')
        self.label_home.setText(main_text)
        self.list_applications.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

        # Layout
        home_layout_top = QHBoxLayout()
        home_layout_top.addWidget(self.label_home, 10, Qt.AlignLeft)
        home_layout_top.addWidget(self.button_refresh, 1,
                                  Qt.AlignRight | Qt.AlignBottom)
        home_layout_top.setSpacing(0)
        self.frame_home_top.setLayout(home_layout_top)

        home_layout_bottom = QVBoxLayout()
        home_layout_bottom.addWidget(self.list_applications)
        home_layout_bottom.addWidget(self.label_status)
        home_layout_bottom.setSpacing(0)
        self.frame_home_bottom.setLayout(home_layout_bottom)

        home_layout = QVBoxLayout()
        home_layout.addWidget(self.frame_home_top)
        home_layout.addWidget(self.frame_home_bottom)
        home_layout.setSpacing(0)
        self.frame_home.setLayout(home_layout)

        layout = QHBoxLayout()
        layout.addWidget(self.frame_home)
        layout.setSpacing(0)
        self.setLayout(layout)

        # Signals
        self.list_applications.sig_application_updated.connect(
            self.sig_application_updated)
        self.list_applications.sig_application_updated.connect(
            self.update_list)
        self.list_applications.sig_status_updated.connect(self.update_status)
        self.button_refresh.clicked.connect(self.refresh_cards)
示例#3
0
class CommunityTab(WidgetBase):
    sig_video_started = Signal(str, int)
    sig_status_updated = Signal(object, int, int, int)
    instances = []

    VIDEOS_LIMIT = 20
    WEBINARS_LIMIT = 20
    EVENTS_LIMIT = 20

    def __init__(self, parent=None, tags=None, content_urls=None):
        super(CommunityTab, self).__init__(parent)

        self._parent = parent
        self._downloaded_urls = []
        self._downloaded_filepaths = []
        self.api = AnacondaAPI()
        self.content_urls = content_urls
        self.content_info = []
        self.step = 0
        self.step_size = 1
        self.tags = tags
        self.timer_load = QTimer()
        self.pixmaps = {}
        self.default_pixmap = QPixmap(VIDEO_ICON_PATH).scaled(
            100, 60, Qt.KeepAspectRatio, Qt.FastTransformation)

        # Widgets
        self.text_filter = LineEditSearch()
        self.frame_community = FrameCommunity()
        self.list = ListWidgetContent()

        # Widget setup
        self.timer_load.setInterval(333)
        self.list.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.text_filter.setPlaceholderText('Search')
        self.text_filter.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.setObjectName("Tab")

        self.list.setMinimumHeight(200)
        fm = self.text_filter.fontMetrics()
        self.text_filter.setMaximumWidth(fm.width('M'*23))

        # Layouts
        hlayout = QHBoxLayout()
        self.filters_layout = QHBoxLayout()

        layout = QVBoxLayout()
        layout.addLayout(hlayout)

        controls_layout = QHBoxLayout()
        controls_layout.addLayout(self.filters_layout)
        controls_layout.addStretch()
        controls_layout.addWidget(self.text_filter)
        layout.addLayout(controls_layout)
        layout.addWidget(self.list)
        self.frame_community.setLayout(layout)

        layout = QHBoxLayout()
        layout.addWidget(self.frame_community)
        self.setLayout(layout)

        # Signals
        self.timer_load.timeout.connect(self.set_content_list)
        self.text_filter.textChanged.connect(self.filter_content)

    def setup_tab(self):
        self.download_content()

    def _json_downloaded(self, worker, output, error):
        url = worker.url
        if url in self._downloaded_urls:
            self._downloaded_urls.remove(url)

        if not self._downloaded_urls:
            self.load_content()

    def download_content(self):
        self._downloaded_urls = []
        self._downloaded_filepaths = []

        if self.content_urls:
            for url in self.content_urls:
                url = url.lower()  # Enforce lowecase... just in case
                fname = url.split('/')[-1] + '.json'
                filepath = os.sep.join([CONTENT_PATH, fname])
                self._downloaded_urls.append(url)
                self._downloaded_filepaths.append(filepath)
                worker = self.api.download_async(url, filepath)
                worker.url = url
                worker.sig_finished.connect(self._json_downloaded)
        else:
            self.load_content()

    def load_content(self, paths=None):
        """
        Load downloaded and bundled content.
        """
        content = []

        # Load downloaded content
        for filepath in self._downloaded_filepaths:
            fname = filepath.split(os.sep)[-1]
            items = []
            if os.path.isfile(filepath):
                with open(filepath, 'r') as f:
                    data = f.read()
                try:
                    items = json.loads(data)
                except Exception as error:
                    logger.error(str((filepath, error)))
            else:
                items = []

            if 'video' in fname:
                for item in items:
                    item['tags'] = ['video']
                    item['uri'] = item['video']
                    uri = item['uri'].split('watch?v=')[-1]
                    url = 'http://img.youtube.com/vi/{0}/0.jpg'.format(uri)
                    item['banner'] = url
                    item['date'] = item.get('date_start', '')
                    item['image_file_path'] = uri + '.jpg'
                items = items[:self.VIDEOS_LIMIT]
            elif 'event' in fname:
                for item in items:
                    item['tags'] = ['event'] + item.get('categories', '').split(', ')
                    item['uri'] = item['url']
                    item['image_file_path'] = item['banner'].split('/')[-1]
                items = items[:self.EVENTS_LIMIT]
            elif 'webinar' in fname:
                for item in items:
                    item['tags'] = ['webinar']
                    item['uri'] = item['url']
                    item['banner'] = item['image']['src']
                    item['image_file_path'] = item['banner'].split('/')[-1]
                items = items[:self.WEBINARS_LIMIT]

            if items:
                content.extend(items)

        # Load bundled content
        with open(LINKS_INFO_PATH, 'r') as f:
            data = f.read()
        items = []
        try:
            items = json.loads(data)
        except Exception as error:
            logger.error(str(filepath, error))
        content.extend(items)

        # Add the omage path to get the full path
        for item in content:
            filename = item['image_file_path']
            item['image_file_path'] = os.path.sep.join([IMAGE_DATA_PATH,
                                                        filename])
        self.content_info = content

        # Save loaded data in a single file
        with open(CONTENT_JSON_PATH, 'w') as f:
            json.dump(content, f)

        self.make_tag_filters()
        self.timer_load.start(random.randint(25, 35))

    def make_tag_filters(self):
        if not self.tags:
            self.tags = set()
            for content_item in self.content_info:
                tags = content_item.get('tags', [])
                for tag in tags:
                    if tag:
                        self.tags.add(tag)
        logger.debug("TAGS: %s", self.tags)
        self.filter_widgets = []
        for tag in self.tags:
            item = QCheckBox(tag.capitalize())
            item.setObjectName(tag.lower())
            item.setChecked(True)
            item.clicked.connect(self.filter_content)
            self.filter_widgets.append(item)
            self.filters_layout.addWidget(item)

    def filter_content(self, text=None):
        """
        Filter content by a search string on all the fields of the item.

        Using comma allows the use of several keywords, e.g. Peter,2015
        """
        text = self.text_filter.text().lower()
        text = [t for t in re.split('\W', text) if t]

        sel_tags = [i.text().lower() for i in self.filter_widgets if i.isChecked()]

        for i in range(self.list.count()):
            item = self.list.item(i)

            all_checks = []
            for t in text:
                t = t.strip()
                checks = (t in item.title.lower() or
                          t in item.venue.lower() or
                          t in ' '.join(item.authors).lower() or
                          t in item.summary.lower())
                all_checks.append(checks)
            all_checks.append(any(tag.lower() in sel_tags for tag in item.tags))

            if all(all_checks):
                item.setHidden(False)
            else:
                item.setHidden(True)

    def set_content_list(self):
        """
        Add items to the list, gradually.

        Called by a timer.
        """
        for i in range(self.step, self.step + self.step_size):
            if i < len(self.content_info):
                item = self.content_info[i]
                banner = item.get('banner', '')
                path = item.get('image_file_path', '')
                content_item = ListItemContent(
                    title=item['title'],
                    subtitle=item.get('subtitle', "") or "",
                    uri=item['uri'], date=item.get('date', '') or "",
                    summary=item.get('summary', '') or "",
                    tags=item.get('tags', []),
                    banner=banner,
                    path=path,
                    pixmap=self.default_pixmap
                    )
                self.list.addItem(content_item)
                content_item.pixmaps = self.pixmaps
                self.download_thumbnail(content_item, banner, path)
            else:
                self.timer_load.stop()
                break
        self.step += self.step_size
        self.filter_content()

    def download_thumbnail(self, item, url, path):
        """
        Download all the video thumbnails.
        """
        worker = self.api.download_async(url, path)
        worker.url = url
        worker.item = item
        worker.sig_finished.connect(self.convert_image)
        logger.debug('Fetching thumbnail %s', url)

    def convert_image(self, worker, output, error):
        """
        Load an image using PIL, and converts it to a QPixmap.

        This was needed as some image libraries are not found in some OS.
        """
        # Needs to come after qtpy imports
        path = output
        if path in self.pixmaps:
            return

        if path:
            if os.path.isfile(path):
                try:
                    if sys.platform == 'darwin':
                        from PIL.ImageQt import ImageQt
                        from PIL import Image

                        image = Image.open(path)
                        image = ImageQt(image)
                        qt_image = QImage(image)
                        pixmap = QPixmap.fromImage(qt_image)
                    else:
                        extension = path.split('.')[-1].upper()

                        if extension in ['PNG', 'JPEG', 'JPG']:
                            pixmap = QPixmap(path, format=extension)
                        else:
                            pixmap = QPixmap(path)

                    self.pixmaps[path] = pixmap
                except (IOError, OSError) as error:
                    logger.error(str(error))

    def update_style_sheet(self, style_sheet=None):
        if style_sheet is None:
            self.style_sheet = load_style_sheet()
        else:
            self.style_sheet = style_sheet

        self.setStyleSheet(self.style_sheet)
        self.list.update_style_sheet(self.style_sheet)
示例#4
0
class PackagesTab(QWidget):
    """
    Packages, environments and commands tab widget.
    """
    sig_status_updated = Signal(object, object, object, object)
    sig_updated = Signal(object, object)
    sig_project_commands_updated = Signal(object, object)
    sig_apps_updated = Signal()

    def __init__(self):
        super(PackagesTab, self).__init__()

        self.api = AnacondaAPI()
        self.path = None
        self.project = None

        # Widgets
        self.list_environments = EditableList(title='Environments')
        self.list_commands = EditableList(title='Commands', min_items=0)
        self.package_manager = CondaPackagesWidget(
            self,
            setup=False,
            data_directory=CHANNELS_PATH,
        )

        # Widget setup
        self.list_environments.button_add.clicked.disconnect()
        self.list_environments.button_edit.setVisible(False)

        # Layout
        top_layout = QHBoxLayout()
        top_layout.addWidget(self.list_environments)
        top_layout.addStretch()
        top_layout.addWidget(self.list_commands)
        top_layout.addStretch()

        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addWidget(self.package_manager)
        self.setLayout(main_layout)

        # Signals
        self.list_commands.sig_item_removed.connect(self.edit_commands)
        self.list_commands.sig_item_edited.connect(self.add_command)
        self.list_commands.sig_item_selected.connect(self.select_command)

        self.list_environments.button_add.clicked.connect(self.add_environment)
        self.list_environments.sig_item_removed.connect(
            self.remove_environment)
        self.list_environments.sig_item_selected.connect(
            self.choose_environment)

        self.package_manager.sig_channels_updated.connect(self.set_channels)

    def load_information(self, path, project=None):
        """
        """
        self.path = path

        if project is None:
            project = self.api.load_project(path)

        logger.debug(str((path, project.name)))
        self.project = project

        # Load commands
        commands = project.commands
        self.list_commands.clear()

        for command in commands:
            item = QListWidgetItem(command)
            self.list_commands.add(item)
        self.list_commands.setCurrentRow(0)

        # Load environments
        environments = project.environments
        self.list_environments.clear()

        selected_row = 0
        for i, environment in enumerate(environments):
            item = QListWidgetItem(environment)
            self.list_environments.add(item)
            if environment == project.default_environment:
                selected_row = i
        self.list_environments.setCurrentRow(selected_row)

        # Load channels
        self.package_manager.set_environment(prefix=project.env_prefix(path))
        self.package_manager.update_channels(project.default_channels,
                                             project.default_channels)

    def set_channels(self, channels, active_channels):
        """
        """
        logger.debug(channels)
        self.project.default_channels = list(active_channels)
        self.project.save(self.path)

    # --- Environments
    # -------------------------------------------------------------------------
    def add_environment(self, text=None, pyver=None):
        """
        Create new basic environment with selectable python version.

        Actually makes new env on disc, in directory within the project
        whose name depends on the env name. New project state is saved.
        Should also sync to spec file.
        """
        dlg = CreateEnvironmentDialog(parent=self,
                                      envs=self.project.environments)

        if dlg.exec_():
            text = dlg.text_name.text().strip()
            pyver = dlg.combo_version.currentText()

        if text:
            self.setEnabled(False)
            QApplication.setOverrideCursor(Qt.BusyCursor)

            # FIXME: This does not seem like a very safe way. What if it fails?
            self.project.default_environment = text
            self.project.environments[text] = "{0}.yaml".format(text)
            prefix = self.project.env_prefix(self.path)

            if pyver:
                pkgs = ['python=' + pyver, 'jupyter']
            else:
                pkgs = ['jupyter']

            channels = self.project.default_channels
            logger.debug(str((prefix, pkgs, channels)))
            worker = self.api.conda_create(prefix=prefix,
                                           pkgs=pkgs,
                                           channels=channels)
            worker.text = text
            worker.sig_finished.connect(self._environment_added)

    def _environment_added(self, worker, output, error):
        """
        Callback for worker after envrionemnt creation has finished.
        """
        text = worker.text
        item = QListWidgetItem(text)
        self.list_environments.add(item)
        self.list_environments.setCurrentRow(
            len(self.project.environments) - 1)
        self.setEnabled(True)
        self.project.save(self.path)
        self.choose_environment()
        QApplication.restoreOverrideCursor()

    def remove_environment(self, item):
        """
        Delete environment from project and project directory.
        """
        text = item.text()
        self.project.default_environment = text
        path = self.project.env_prefix(self.path)
        logger.debug(str((path, text)))
        shutil.rmtree(path)
        self.project.environments.pop(text)
        self.project.save(self.path)
        self.choose_environment()

    def choose_environment(self):
        """
        The user has picked a different environment.

        Refresh the package list and save the new state of the project.
        """
        current_item_text = self.list_environments.currentItem().text()
        logger.debug(current_item_text)
        self.project.default_environment = current_item_text
        self.project.save(self.path)
        prefix = self.project.env_prefix(self.path)
        self.package_manager.set_environment(prefix=prefix)
        self.package_manager.setup()

    # --- Commands
    # -------------------------------------------------------------------------
    def add_command(self, command=None):
        """
        Add a new command in the commands list. Adds to the bottom of the list.
        """
        if command:
            item = QListWidgetItem(command)
            self.list_commands.add(item)

        commands = [
            self.list_commands.item(i).text()
            for i in range(self.list_commands.count())
        ]
        self.project.commands = commands
        self.project.save(self.path)
        self.sig_project_commands_updated.emit(self.path,
                                               self.project.commands)
        logger.debug(self.project.commands)

    def edit_commands(self):
        """
        """
        commands = [
            self.list_commands.item(i).text()
            for i in range(self.list_commands.count())
        ]
        self.project.commands = commands
        self.project.save(self.path)
        self.sig_project_commands_updated.emit(self.path,
                                               self.project.commands)
        logger.debug(self.project.commands)

    def select_command(self):
        """
        """
        self.sig_apps_updated.emit()
        logger.debug(self.project.commands)
示例#5
0
class HomeTab(WidgetBase):
    sig_pointer_updated = Signal(object, object)
    sig_status_updated = Signal(object, object, object, object)
    sig_application_updated = Signal(object, object)

    def __init__(self, parent=None):
        super(HomeTab, self).__init__(parent)

        # Variables
        self._parent = parent
        self.api = AnacondaAPI()
        self.setObjectName('Tab')

        # Widgetsugh
        main_icon = QLabel()
        self.list_applications = ListWidgetApplication()
        self.apps_model = None
        self.apps = None
        self.app_timers = None
        self.button_refresh = ButtonHomeRefresh('Refresh')
        self.frame_home = FrameHome(self)
        self.frame_home_top = FrameHomeTop(self)
        self.frame_home_bottom = FrameHomeTop(self)
        self.label_home = LabelHome('')
        self.label_status = QLabel('')
        self.timer_refresh = QTimer()

        # Widget setup
        pixmap = QPixmap(images.ANACONDA_ICON_128_PATH)
        main_icon.setPixmap(pixmap)
        main_text = ('My Applications')
        self.label_home.setText(main_text)
        self.list_applications.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

        # Layout
        home_layout_top = QHBoxLayout()
        home_layout_top.addWidget(self.label_home, 10, Qt.AlignLeft)
        home_layout_top.addWidget(self.button_refresh, 1,
                                  Qt.AlignRight | Qt.AlignBottom)
        home_layout_top.setSpacing(0)
        self.frame_home_top.setLayout(home_layout_top)

        home_layout_bottom = QVBoxLayout()
        home_layout_bottom.addWidget(self.list_applications)
        home_layout_bottom.addWidget(self.label_status)
        home_layout_bottom.setSpacing(0)
        self.frame_home_bottom.setLayout(home_layout_bottom)

        home_layout = QVBoxLayout()
        home_layout.addWidget(self.frame_home_top)
        home_layout.addWidget(self.frame_home_bottom)
        home_layout.setSpacing(0)
        self.frame_home.setLayout(home_layout)

        layout = QHBoxLayout()
        layout.addWidget(self.frame_home)
        layout.setSpacing(0)
        self.setLayout(layout)

        # Signals
        self.list_applications.sig_application_updated.connect(
            self.sig_application_updated)
        self.list_applications.sig_application_updated.connect(
            self.update_list)
        self.list_applications.sig_status_updated.connect(self.update_status)
        self.button_refresh.clicked.connect(self.refresh_cards)

    def update_list(self, name=None, version=None):
        self.set_applications()

    def refresh_cards(self):
        """
        List widget items sometimes are hidden on resize. This method tries
        to compensate for that refreshing and repainting periodically.
        """
        self.list_applications.update_style_sheet()
        self.list_applications.repaint()
        for item in self.list_applications.items():
            if not item.widget.isVisible():
                item.widget.repaint()

    def update_status(self, text):
        self.label_status.setText(text)
        for item in self.list_applications._items:
            item.widget.setVisible(True)
            item.setHidden(False)
            item.widget.repaint()

    def setup_tab(self, apps):
        self.apps_packages = apps
        self.set_applications()

    def set_applications(self):
        """
        Build the list of applications present in the current conda
        environment
        """
        paths = get_projects_path()
        project_dict = self.api.load_applications(paths)

        APPS_DESCRIPTIONS = {
            'glueviz':
            'Multidimensional data visualization across files. '
            'Explore relationships within and among related '
            'datasets.',
            'notebook':
            'Web-based, interactive computing notebook '
            'environment. Edit and run human-readable docs while '
            'describing the data analysis.',
            'orange-app':
            'Component based data mining framework. Data '
            'visualization and data analysis for novice and '
            'expert. Interactive workflows with a large '
            'toolbox.',
            'qtconsole':
            'PyQt GUI that supports inline figures, proper '
            'multiline editing with syntax highlighting, '
            'graphical calltips, and more.',
            'spyder':
            'Scientific PYthon Development EnviRonment. Powerful '
            'Python IDE with advanced editing, interactive '
            'testing, debugging and introspection features',
        }

        # Add applications
        all_applications = []
        project_installed = []
        project_not_installed = []
        for project_path in project_dict:
            project = project_dict[project_path]
            prefix = self.api.ROOT_PREFIX
            version = self.api.conda_package_version(prefix, pkg=project.name)
            if version:
                project_installed.append(project_path)
            else:
                project_not_installed.append(project_path)

        for project_path in project_installed + project_not_installed:
            project = project_dict[project_path]

            if project.icon:
                pixmap = self.api.load_icon(project_path,
                                            project,
                                            as_pixmap=True)
            else:
                pixmap = None

            if project.commands:
                command = project.commands[0]
            else:
                command = None

            # Temporal FIX, only install APPS on ROOT


#            prefix = project.env_prefix(project_path)
            prefix = self.api.ROOT_PREFIX
            name = project.name

            if project.is_conda_app:
                version = self.api.conda_package_version(prefix, pkg=name)
            else:
                version = project.version

            description = APPS_DESCRIPTIONS.get(name, '')
            item = ListItemApplication(name=name,
                                       description=description,
                                       path=project_path,
                                       dev_tool=project.dev_tool,
                                       pixmap=pixmap,
                                       version=version,
                                       versions=None,
                                       command=command,
                                       prefix=prefix,
                                       is_conda_app=project.is_conda_app)
            all_applications.append(item)

        self.list_applications.clear()

        for i in all_applications:
            self.list_applications.addItem(i)
        empty = ListItemEmpty()
        self.list_applications.addItem(empty)
        self.update_versions(self.apps_packages)
        self.timer_refresh.start()

    def update_versions(self, apps=None):
        self.items = []

        for i in range(self.list_applications.count()):
            item = self.list_applications.item(i)
            self.items.append(item)
            if isinstance(item, ListItemApplication):
                name = item.name
                meta = apps.get(name, None)
                if meta:
                    versions = meta['versions']
                    version = self.api.get_dev_tool_version(item.path)
                    item.update_versions(version, versions)

    def update_style_sheet(self, style_sheet=None):
        if style_sheet is None:
            style_sheet = load_style_sheet()
        self.list_applications.update_style_sheet(style_sheet=style_sheet)
        self.setStyleSheet(style_sheet)
示例#6
0
    def __init__(self, parent=None):
        super(ProjectWidget, self).__init__(parent)

        # Variables
        self.api = AnacondaAPI()
        self._parent = parent
        self.path = None
        self.project = None
        self.timer = QTimer()
        self.timeout = 6000

        # Widgets
        self.text_name = EditableLineEdit('Name', '')
        self.text_location = EditablePathEdit('Location',
                                              '',
                                              caption="Select new project "
                                              "directory",
                                              basedir=get_home_dir())
        self.text_icon = EditablePathEdit(
            'Icon',
            '',
            fileselect=True,
            caption='Select icon image file',
            filters="Image files (*.png *.jpg *.jpeg)",
            basedir=get_home_dir())
        self.button_launch = QPushButton('Launch')
        self.button_upload = QPushButton('Upload to Anaconda Cloud')

        self.tabs = QTabWidget(parent=self)
        self.tab_packages = PackagesTab()
        #        self.tab_advanced = AdvancedTab()
        self.tab_explorer = ExplorerTab()

        # Widget Setup
        self.setWindowTitle('Project Editor')
        self.button_upload.setObjectName('ButtonUpdate')
        self.button_launch.setObjectName('ButtonUpdate')
        self.tabs.addTab(self.tab_explorer, 'Files')
        self.tabs.addTab(self.tab_packages, 'Packages')
        #        self.tabs.addTab(self.tab_advanced, 'Advanced options')
        self.text_icon._text.setVisible(False)

        # Layouts
        project_buttons = QHBoxLayout()
        project_buttons.addWidget(self.button_upload)
        #        project_buttons.addWidget(self.button_launch)
        project_buttons.addStretch()

        information_layout = QHBoxLayout()
        information_layout.addWidget(self.text_name, 2)
        information_layout.addWidget(self.text_location, 4)
        information_layout.addWidget(self.text_icon, 1)

        main_layout = QVBoxLayout()
        #        main_layout.addLayout(project_buttons, 1)
        #        main_layout.addSpacing(6)
        main_layout.addLayout(information_layout, 1)
        main_layout.addWidget(self.tabs, 10)
        main_layout.addWidget(self.button_launch, 0, Qt.AlignRight)

        self.setLayout(main_layout)

        # Signals
        self.button_launch.clicked.connect(self.launch)

        self.tab_explorer.explorer.sig_home_clicked.connect(
            self.set_explorer_path)
        self.tab_explorer.treewidget.sig_edit.connect(self.edit_file)
        self.tab_explorer.treewidget.sig_open_py.connect(self.run_python_file)
        self.tab_explorer.treewidget.sig_open_py_con.connect(
            self.run_python_console)
        self.tab_explorer.treewidget.sig_open_notebook.connect(
            self.run_notebook)
        self.tab_explorer.treewidget.sig_add_endpoint.connect(
            self.make_command_from_file)

        self.tab_packages.sig_project_commands_updated.connect(
            self.sig_project_commands_updated)
        self.tab_packages.sig_apps_updated.connect(self.sig_apps_updated)

        self.timer.timeout.connect(self._launched)
        self.text_location.sig_text_changed.connect(self.update_location)
        self.text_name.sig_text_changed.connect(self.update_name)
        self.text_icon.sig_text_changed.connect(self.update_icon)

        # Setup
        self.button_upload.setDisabled(True)
        self.button_upload.setVisible(False)
示例#7
0
class ProjectWidget(QWidget):
    """
    Project information widget.
    """
    sig_apps_updated = Signal()
    sig_project_updated = Signal()
    sig_project_commands_updated = Signal(object, object)
    sig_project_name_updated = Signal(object, object, object)
    sig_project_icon_updated = Signal(object, object, object)
    sig_status_updated = Signal(object, object, object, object)

    def __init__(self, parent=None):
        super(ProjectWidget, self).__init__(parent)

        # Variables
        self.api = AnacondaAPI()
        self._parent = parent
        self.path = None
        self.project = None
        self.timer = QTimer()
        self.timeout = 6000

        # Widgets
        self.text_name = EditableLineEdit('Name', '')
        self.text_location = EditablePathEdit('Location',
                                              '',
                                              caption="Select new project "
                                              "directory",
                                              basedir=get_home_dir())
        self.text_icon = EditablePathEdit(
            'Icon',
            '',
            fileselect=True,
            caption='Select icon image file',
            filters="Image files (*.png *.jpg *.jpeg)",
            basedir=get_home_dir())
        self.button_launch = QPushButton('Launch')
        self.button_upload = QPushButton('Upload to Anaconda Cloud')

        self.tabs = QTabWidget(parent=self)
        self.tab_packages = PackagesTab()
        #        self.tab_advanced = AdvancedTab()
        self.tab_explorer = ExplorerTab()

        # Widget Setup
        self.setWindowTitle('Project Editor')
        self.button_upload.setObjectName('ButtonUpdate')
        self.button_launch.setObjectName('ButtonUpdate')
        self.tabs.addTab(self.tab_explorer, 'Files')
        self.tabs.addTab(self.tab_packages, 'Packages')
        #        self.tabs.addTab(self.tab_advanced, 'Advanced options')
        self.text_icon._text.setVisible(False)

        # Layouts
        project_buttons = QHBoxLayout()
        project_buttons.addWidget(self.button_upload)
        #        project_buttons.addWidget(self.button_launch)
        project_buttons.addStretch()

        information_layout = QHBoxLayout()
        information_layout.addWidget(self.text_name, 2)
        information_layout.addWidget(self.text_location, 4)
        information_layout.addWidget(self.text_icon, 1)

        main_layout = QVBoxLayout()
        #        main_layout.addLayout(project_buttons, 1)
        #        main_layout.addSpacing(6)
        main_layout.addLayout(information_layout, 1)
        main_layout.addWidget(self.tabs, 10)
        main_layout.addWidget(self.button_launch, 0, Qt.AlignRight)

        self.setLayout(main_layout)

        # Signals
        self.button_launch.clicked.connect(self.launch)

        self.tab_explorer.explorer.sig_home_clicked.connect(
            self.set_explorer_path)
        self.tab_explorer.treewidget.sig_edit.connect(self.edit_file)
        self.tab_explorer.treewidget.sig_open_py.connect(self.run_python_file)
        self.tab_explorer.treewidget.sig_open_py_con.connect(
            self.run_python_console)
        self.tab_explorer.treewidget.sig_open_notebook.connect(
            self.run_notebook)
        self.tab_explorer.treewidget.sig_add_endpoint.connect(
            self.make_command_from_file)

        self.tab_packages.sig_project_commands_updated.connect(
            self.sig_project_commands_updated)
        self.tab_packages.sig_apps_updated.connect(self.sig_apps_updated)

        self.timer.timeout.connect(self._launched)
        self.text_location.sig_text_changed.connect(self.update_location)
        self.text_name.sig_text_changed.connect(self.update_name)
        self.text_icon.sig_text_changed.connect(self.update_icon)

        # Setup
        self.button_upload.setDisabled(True)
        self.button_upload.setVisible(False)

    # --- Global methods
    # -------------------------------------------------------------------------
    def project_updated(self, environments, commands):
        """
        """
        self.sig_project_updated.emit()

    def update_status(self, message='', timeout=0):
        """
        Update project tab status.
        """
        self.sig_status_updated.emit(message, timeout, -1, -1)

    def load_project_information(self, path, project=None):
        """
        Load project information on project widget.
        """
        if project is None:
            project = self.api.load_project(path)
        self.path = path
        self.project = project

        self.text_name.setText(project.name)
        self.text_location.setText(path)

        self.set_explorer_path(path)
        self.tab_packages.load_information(path)

        self.text_location.button_edit.setVisible(False)  # FIXME: Temporal!
        if self.is_default():
            self.button_launch.setDisabled(True)
            self.text_name.button_edit.setDisabled(True)
            self.text_icon.button_edit.setDisabled(True)
            self.text_location.button_edit.setDisabled(True)
            self.tab_packages.list_commands.setVisible(False)
            self.tab_packages.list_environments.setVisible(False)
        else:
            if self.tab_packages.list_commands.count():
                self.button_launch.setDisabled(False)
            else:
                self.button_launch.setDisabled(True)
            self.text_icon.button_edit.setDisabled(False)
            self.text_name.button_edit.setDisabled(False)
            self.text_location.button_edit.setDisabled(True)
            self.tab_packages.list_commands.setVisible(True)
            self.tab_packages.list_environments.setVisible(True)

    def update_name(self, old_name, new_name):
        """
        Update project name.
        """
        if self.project is not None:
            self.project.name = new_name
            logger.debug(new_name)
            self.project.save(self.path)
            self.sig_project_name_updated.emit(self.path, old_name, new_name)

    def update_location(self, old_path, new_path):
        """
        FIXME: To be implemented
        """
        path = getexistingdirectory(caption="New location", basedir=self.path)
        logger.debug(path)
        if path:
            try:
                if os.path.exists(path):
                    os.rmdir(path)
                shutil.move(self.path, path)
                projects_path = CONF.get('main', 'projects_path', [])
                projects_path.remove(self.path)
                projects_path.append(path)
                CONF.set('main', 'projects_path', projects_path)
                self.project.path = path
                self.path = path
                self.sig_project_updated.emit()
            except Exception as e:
                print(e)

    def update_icon(self, old_image_path, new_image_path):
        """
        Update project icon/image.
        """
        self.project.icon = 'icon.png'
        logger.debug(new_image_path)
        self.project.save(self.path)
        self.sig_project_icon_updated.emit(self.path, old_image_path,
                                           new_image_path)

    def launch(self):
        """
        Execute the selected command in the current project.
        """
        list_commands = self.tab_packages.list_commands
        if list_commands.count() != 0:
            self.button_launch.setDisabled(True)
            self.timer.setInterval(self.timeout)
            self.timer.start()
            update_pointer(Qt.BusyCursor)
            command = list_commands.currentItem().text()
            logger.debug(str((self.path, command)))
            launch(self.path, command)

    def _launched(self):
        """
        """
        self.button_launch.setDisabled(False)
        update_pointer()

    def select_icon(self):
        """
        """
        out = getopenfilename(
            caption='Select icon',
            filters='PNG Image (*.png)',
        )
        logger.debug(str(out))
        if out[0]:
            with open(out[0], 'rb') as f:
                image = f.read()

            self.project.icon = base64.b64encode(image)
            self.project.save(self.path)
            self.load_icon()

    def is_default(self):
        """
        Check if current project is the default project.
        """
        return (self.text_name.text() == 'default'
                and self.path == DEFAULT_PROJECT_PATH)

    # --- Explorer Commands
    # -------------------------------------------------------------------------
    def set_explorer_path(self, path=None):
        """
        """
        path = path or self.path
        self.tab_explorer.treewidget.set_current_folder(path)

    def edit_file(self, filename=""):
        """
        Open file with spyder.
        """
        logger.debug(filename)
        subprocess.Popen(['spyder', filename])

    def run_notebook(self, filename=""):
        """
        Start notebook server.
        """
        logger.debug(filename)
        run_notebook(self.path, self.project, filename)

    def run_python_file(self, filename=""):
        """
        Execute python in environment.
        """
        logger.debug(filename)
        run_python_file(self.path, self.project, filename)

    def run_python_console(self, filename=""):
        """
        Execute python in env with console.
        """
        logger.debug(filename)
        run_python_console(self.path, self.project, filename)

    def make_command_from_file(self, filename):
        """
        Add a command in the commands list based on the current file.
        """
        logger.debug(filename)
        bin_path = os.sep.join([self.project.env_prefix(self.path), 'bin'])

        if filename.endswith('.py'):
            cmd = 'python ' + filename.replace(self.path, '{PREFIX}')

        elif filename.endswith('.ipynb'):
            cmd = 'jupyter notebook ' + filename.replace(self.path, '{PREFIX}')

        elif (os.path.isfile(filename) and os.access(filename, os.X_OK)
              and bin_path in filename):
            new_bin_path = os.sep.join(
                [self.project.env_prefix(self.path), 'bin'])
            cmd = filename.replace(new_bin_path, '')
        else:
            return
        self.add_command(text=cmd)

    def upload_notebook(self, filepath):
        """
        """
        pass

    # --- Commands
    # -------------------------------------------------------------------------
    def add_command(self, text):
        """
        New endpoint: go into edit mode.
        """
        self.tab_packages.add_command(text)
        logger.debug(text)
示例#8
0
class PreferencesDialog(DialogBase):
    sig_urls_updated = Signal(str, str)
    sig_show_application_environments = Signal(bool)

    def __init__(self, *args, **kwargs):
        super(PreferencesDialog, self).__init__(*args, **kwargs)

        self.api = AnacondaAPI()
        self.widgets_changed = set()
        self.widgets = []

        # Widgets
        self.button_ok = QPushButton('Ok')
        self.button_cancel = ButtonCancel('Cancel')
        self.button_reset = QPushButton('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_ok)
        buttons_layout.addWidget(self.button_cancel)

        main_layout = QVBoxLayout()
        main_layout.addLayout(self.grid_layout)
        main_layout.addSpacing(40)
        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):
        return CONF.get('main', option, None)

    def set_option(self, option, value, default=False):
        CONF.set('main', option, value)

    def set_option_default(self, option):
        default = CONF.get_default('main', option)
        self.set_option(option, default)

    def create_widget(self,
                      widget=None,
                      label=None,
                      option=None,
                      hint=None,
                      check=None):
        if hint:
            widget.setPlaceholderText(hint)

        config_value = self.get_option(option)
        widget.label = QLabel(label)
        widget.option = option
        widget.set_value(config_value)
        widget.label_information = QLabel()
        widget.label_information.setMinimumWidth(20)
        widget.label_information.setMaximumWidth(20)

        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)
        form_widget.setLayout(h_layout)

        if check:
            widget.check_value = lambda value: check(value)
        else:
            widget.check_value = lambda value: (True, '')

        self.widgets.append(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, regex=None, check=None):
        widget = QLineEdit()
        widget.setAttribute(Qt.WA_MacShowFocusRect, False)
        widget.setMinimumWidth(250)

        if regex:
            regex_validator = QRegExpValidator(QRegExp(regex))
            widget.setValidator(regex_validator)

        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)

    def create_checkbox(self, label, option, hint=None):
        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))
        widget.stateChanged.connect(
            lambda v=None, w=widget: self.options_changed(widget=w))
        self.create_widget(widget=widget,
                           option=option,
                           label=label,
                           hint=hint)

    def options_changed(self, value=None, widget=None):
        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)))

    # --- API
    # -------------------------------------------------------------------------
    def setup(self):
        self.create_textbox('Anaconda API domain',
                            'anaconda_api_url',
                            check=self.is_valid_api)
        self.create_textbox('Conda domain',
                            'conda_url',
                            check=self.is_valid_url)
        self.create_checkbox('Provide analytics', 'provide_analytics')
        self.create_checkbox("Hide quit dialog", 'hide_quit_dialog')


#        self.create_checkbox('Show application<br>environments',
#                             'show_application_environments')

    def warn(self, widget, text=None):
        """
        """
        label = widget.label_information
        if text:
            pixmap = QPixmap(WARNING_ICON)
            label.setPixmap(
                pixmap.scaled(16, 16, Qt.KeepAspectRatio,
                              Qt.SmoothTransformation))
            label.setToolTip(str(text))
        else:
            label.setPixmap(QPixmap())
            label.setToolTip('')

    # --- Checkers
    # -------------------------------------------------------------------------
    def is_valid_url(self, url):
        """
        Chek 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 url'
        return output, error

    def is_valid_api(self, url):
        """
        Chek if a given URL is a valid anaconda api endpoint.
        """
        output = self.api.download_is_valid_api_url(url, non_blocking=False)
        error = ''
        if not output:
            error = 'Invalid Anaconda API url.'
        return output, error

    def reset_to_defaults(self):
        """
        """
        # ASK!
        for widget in self.widgets:
            option = widget.option
            self.set_option_default(option)
            value = self.get_option(option)
            widget.set_value(value)

    def accept(self):
        """
        Qt override.
        """
        sig_updated = False
        for widget in self.widgets_changed:
            option = widget.option
            value = widget.get_value()
            check, error = widget.check_value(value)

            if check:
                self.set_option(option, value)
                self.warn(widget)
            else:
                self.button_ok.setDisabled(True)
                widget.set_warning()
                self.warn(widget, error)
                return

            if widget.option == 'conda_url':
                sig_updated = True

            if widget.option == 'anaconda_api_url':
                sig_updated = True

            if widget.option == 'show_application_environments':
                self.sig_show_application_environments.emit(value)

        for widget in self.widgets:
            if widget.option == 'anaconda_api_url':
                anaconda_api_url = widget.get_value()
            elif widget.option == 'conda_url':
                conda_url = widget.get_value()

        if sig_updated:
            self.sig_urls_updated.emit(anaconda_api_url, conda_url)

        super(PreferencesDialog, self).accept()
示例#9
0
    def __init__(self,
                 name=None,
                 description=None,
                 command=None,
                 pixmap=None,
                 version=None,
                 versions=None,
                 path=None,
                 dev_tool=True,
                 prefix=None,
                 is_conda_app=False,
                 packages_widget=None):
        super(ListItemApplication, self).__init__()

        self.api = AnacondaAPI()
        self.command = command
        self.dev_tool = dev_tool
        self.installed = False
        self.is_conda_app = is_conda_app
        self.name = name
        self.path = path
        self.pixmap = pixmap if pixmap else QPixmap(ANACONDA_ICON_64_PATH)
        self.prefix = prefix
        self.timeout = 10000  # In miliseconds
        self.version = version
        self.versions = versions
        self.packages_widget = packages_widget

        # Widgets
        self.button_install = ButtonApplicationInstall("Install")
        self.button_launch = ButtonApplicationLaunch("Launch")
        self.button_options = ButtonApplicationOptions()
        self.label_icon = LabelApplicationIcon()
        self.label_name = LabelApplicationName(name)
        self.label_description = LabelApplicationDescription(description)
        #        self.label_update = LabelApplicationUpdate()
        self.button_version = ButtonApplicationVersion(to_text_string(version))
        self.label_spinner = LabelApplicationSpinner()
        #        self.label_version = LabelApplicationVersion(to_text_string(version))
        self.menu_options = QMenu('Application options')
        self.menu_versions = QMenu('Install specific version')
        self.movie_spinner = QMovie(SPINNER_WHITE_16_PATH)
        self.timer = QTimer()
        self.widget = WidgetApplication()

        # Widget setup
        self.button_version.setFocusPolicy(Qt.NoFocus)
        self.label_name.setToolTip(description)
        self.label_description.setAlignment(Qt.AlignCenter)
        self.movie_spinner.start()
        self.timer.setInterval(self.timeout)
        self.timer.setSingleShot(True)
        self.label_icon.setToolTip(description)
        self.label_icon.setPixmap(
            self.pixmap.scaled(self.ICON_SIZE, self.ICON_SIZE,
                               Qt.KeepAspectRatio, Qt.SmoothTransformation))
        self.label_icon.setAlignment(Qt.AlignCenter)
        self.label_name.setAlignment(Qt.AlignCenter)
        self.label_name.setWordWrap(True)
        self.label_description.setWordWrap(True)
        self.label_description.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
        self.label_spinner.setVisible(False)
        self.label_spinner.setMinimumWidth(16)
        self.label_spinner.setMinimumHeight(16)

        # Layouts
        layout = QVBoxLayout()
        layout.addWidget(self.button_options, 0, Qt.AlignRight)
        layout.addWidget(self.label_icon, 0, Qt.AlignCenter)
        layout.addWidget(self.label_name, 0, Qt.AlignCenter)
        layout.addWidget(self.label_description, 0, Qt.AlignCenter)

        #        hlayout = QHBoxLayout()
        #        hlayout.addWidget(self.label_update)
        #        hlayout.addWidget(self.label_version)
        #        layout.addLayout(hlayout)
        #        layout.addWidget(self.label_version, 0, Qt.AlignCenter)
        layout.addWidget(self.button_version, 0, Qt.AlignCenter)
        layout.addWidget(self.label_spinner, 0, Qt.AlignCenter)
        layout.addWidget(self.button_launch, 0, Qt.AlignCenter)
        layout.addWidget(self.button_install, 0, Qt.AlignCenter)

        self.widget.setLayout(layout)
        self.widget.setStyleSheet(load_style_sheet())
        self.setSizeHint(self.widget.sizeHint())

        # Signals
        self.button_install.clicked.connect(self.install_application)
        self.button_launch.clicked.connect(self.launch_application)
        self.button_options.clicked.connect(self.actions_menu_requested)
        self.timer.timeout.connect(self._application_launched)

        # Setup
        self.update_status()
示例#10
0
class ListItemApplication(QListWidgetItem):
    """
    Item with custom widget for the applications list.
    """
    ICON_SIZE = 48

    def __init__(self,
                 name=None,
                 description=None,
                 command=None,
                 pixmap=None,
                 version=None,
                 versions=None,
                 path=None,
                 dev_tool=True,
                 prefix=None,
                 is_conda_app=False,
                 packages_widget=None):
        super(ListItemApplication, self).__init__()

        self.api = AnacondaAPI()
        self.command = command
        self.dev_tool = dev_tool
        self.installed = False
        self.is_conda_app = is_conda_app
        self.name = name
        self.path = path
        self.pixmap = pixmap if pixmap else QPixmap(ANACONDA_ICON_64_PATH)
        self.prefix = prefix
        self.timeout = 10000  # In miliseconds
        self.version = version
        self.versions = versions
        self.packages_widget = packages_widget

        # Widgets
        self.button_install = ButtonApplicationInstall("Install")
        self.button_launch = ButtonApplicationLaunch("Launch")
        self.button_options = ButtonApplicationOptions()
        self.label_icon = LabelApplicationIcon()
        self.label_name = LabelApplicationName(name)
        self.label_description = LabelApplicationDescription(description)
        #        self.label_update = LabelApplicationUpdate()
        self.button_version = ButtonApplicationVersion(to_text_string(version))
        self.label_spinner = LabelApplicationSpinner()
        #        self.label_version = LabelApplicationVersion(to_text_string(version))
        self.menu_options = QMenu('Application options')
        self.menu_versions = QMenu('Install specific version')
        self.movie_spinner = QMovie(SPINNER_WHITE_16_PATH)
        self.timer = QTimer()
        self.widget = WidgetApplication()

        # Widget setup
        self.button_version.setFocusPolicy(Qt.NoFocus)
        self.label_name.setToolTip(description)
        self.label_description.setAlignment(Qt.AlignCenter)
        self.movie_spinner.start()
        self.timer.setInterval(self.timeout)
        self.timer.setSingleShot(True)
        self.label_icon.setToolTip(description)
        self.label_icon.setPixmap(
            self.pixmap.scaled(self.ICON_SIZE, self.ICON_SIZE,
                               Qt.KeepAspectRatio, Qt.SmoothTransformation))
        self.label_icon.setAlignment(Qt.AlignCenter)
        self.label_name.setAlignment(Qt.AlignCenter)
        self.label_name.setWordWrap(True)
        self.label_description.setWordWrap(True)
        self.label_description.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
        self.label_spinner.setVisible(False)
        self.label_spinner.setMinimumWidth(16)
        self.label_spinner.setMinimumHeight(16)

        # Layouts
        layout = QVBoxLayout()
        layout.addWidget(self.button_options, 0, Qt.AlignRight)
        layout.addWidget(self.label_icon, 0, Qt.AlignCenter)
        layout.addWidget(self.label_name, 0, Qt.AlignCenter)
        layout.addWidget(self.label_description, 0, Qt.AlignCenter)

        #        hlayout = QHBoxLayout()
        #        hlayout.addWidget(self.label_update)
        #        hlayout.addWidget(self.label_version)
        #        layout.addLayout(hlayout)
        #        layout.addWidget(self.label_version, 0, Qt.AlignCenter)
        layout.addWidget(self.button_version, 0, Qt.AlignCenter)
        layout.addWidget(self.label_spinner, 0, Qt.AlignCenter)
        layout.addWidget(self.button_launch, 0, Qt.AlignCenter)
        layout.addWidget(self.button_install, 0, Qt.AlignCenter)

        self.widget.setLayout(layout)
        self.widget.setStyleSheet(load_style_sheet())
        self.setSizeHint(self.widget.sizeHint())

        # Signals
        self.button_install.clicked.connect(self.install_application)
        self.button_launch.clicked.connect(self.launch_application)
        self.button_options.clicked.connect(self.actions_menu_requested)
        self.timer.timeout.connect(self._application_launched)

        # Setup
        self.update_status()

    # --- Callbacks
    # -------------------------------------------------------------------------
    def _application_launched(self):
        """
        """
        self.button_launch.setDisabled(False)
        update_pointer()

    def _application_installed(self, worker, output, error):
        """
        """
        self.handle_action_finished(worker, output, error)

    def _application_updated(self, worker, output, error):
        """
        """
        self.handle_action_finished(worker, output, error)

    def _application_removed(self, worker, output, error):
        """
        """
        self.handle_action_finished(worker, output, error)

    # --- Helpers
    # -------------------------------------------------------------------------
    def _partial_output_ready(self, worker, output, error):
        """
        """
        message = None
        progress = (0, 0)

        if isinstance(output, dict):
            progress = (output.get('progress',
                                   None), output.get('maxval', None))
            name = output.get('name', None)
            fetch = output.get('fetch', None)

            if fetch:
                message = "Downloading <b>{0}</b>...".format(fetch)

            if name:
                self._current_action_name = name
                message = "Linking <b>{0}</b>...".format(name)

        logger.debug(message)
        self.widget.sig_status_updated.emit(message)

    def update_style_sheet(self, style_sheet=None):
        if style_sheet is None:
            style_sheet = load_style_sheet()
        self.menu_options.setStyleSheet(style_sheet)
        self.menu_versions.setStyleSheet(style_sheet)

    def actions_menu_requested(self):
        """
        Create and display options menu for the currently selected application.
        """
        self.menu_options.clear()
        self.menu_versions.clear()

        # Add versions menu
        versions = self.versions if self.versions else []
        version_actions = []
        for version in reversed(versions):
            action = create_action(self.widget,
                                   version,
                                   triggered=lambda value, version=version:
                                   self.install_application(version=version))

            action.setCheckable(True)
            if self.version == version:
                action.setChecked(True)
                action.setDisabled(True)

            version_actions.append(action)

        update_action = create_action(
            self.widget,
            'Update application',
            triggered=lambda: self.update_application())

        if versions and versions[-1] == self.version:
            update_action.setDisabled(True)
        else:
            update_action.setDisabled(False)

        remove_action = create_action(
            self.widget,
            'Remove application',
            triggered=lambda: self.remove_application())
        remove_action.setEnabled(self.installed)

        actions = [update_action, remove_action, None, self.menu_versions]
        add_actions(self.menu_options, actions)
        add_actions(self.menu_versions, version_actions)
        offset = QPoint(self.button_options.width(), 0)
        position = self.button_options.mapToGlobal(QPoint(0, 0))
        self.menu_versions.setEnabled(bool(versions))
        self.menu_options.move(position + offset)
        self.menu_options.exec_()

    def update_status(self):
        if self.prefix:
            self.version = self.api.conda_package_version(self.prefix,
                                                          pkg=self.name)
        self.installed = bool(self.version)
        if (self.versions and self.version != self.versions[-1]
                and self.installed):
            self.button_version.setIcon(QIcon(CONDA_MANAGER_UPGRADE_ARROW))
            self.button_version.setStyleSheet(
                "ButtonApplicationVersion {color: #0071a0;}")
            self.button_version.setToolTip('Version {0} available'.format(
                self.versions[-1]))
        else:
            self.button_version.setIcon(QIcon())
            self.button_version.setStyleSheet(
                "ButtonApplicationVersion {color: black;}")

        self.button_install.setVisible(not self.installed)
        self.button_launch.setVisible(self.installed)

    def set_loading(self, value):
        self.button_launch.setDisabled(value)
        self.button_install.setDisabled(value)
        self.button_options.setDisabled(value)

        if value:
            self.label_spinner.setMovie(self.movie_spinner)
        else:
            self.label_spinner.setMovie(None)
            if self.version is None:
                version = self.versions[-1]
            else:
                version = self.version
            self.button_version.setText(version)

        self.label_spinner.setVisible(value)
        self.button_version.setVisible(not value)

    def handle_action_finished(self, worker, output, error):
        if not isinstance(output, dict):
            output = {}
        success = output.get('success', True)

        if error or not success:
            # Error might be harmless if no decoding was possible...
            # Success deserves some sort of messagebox
            logger.error(error)
        self.widget.sig_application_updated.emit(self.name, self.version)

        self.update_status()
        self.set_loading(False)

    def update_versions(self, version=None, versions=None):
        """
        Update button visibility depending on update availability.
        """
        update = versions[-1] != version
        logger.debug(str((self.name, self.dev_tool, self.installed)))

        if self.installed and version:
            self.button_options.setVisible(True)
            self.button_version.setText(version)
            self.button_version.setVisible(True)
        elif not self.installed and versions:
            self.button_install.setEnabled(True)
            self.button_version.setText(versions[-1])
            self.button_version.setVisible(True)

        self.versions = versions
        self.version = version
        self.update_status()

    # --- Public API
    # ------------------------------------------------------------------------
    def install_application(self, value=None, version=None):
        """
        Update the application on the defined prefix environment.

        This is used for both normal install and specific version install.
        """
        if version:
            self.version = version
        else:
            self.version = self.versions[-1]
            version = self.versions[-1]

        pkg = '{0}={1}'.format(self.name, version)
        pkgs = [pkg]
        logger.debug(str((pkg, self.dev_tool)))

        # Check if environment exists and then create or install
        #        is_installed = self.api.conda_package_version(prefix=self.prefix,
        #                                                      pkg=self.name)
        #        pkgs = [pkg] + self.BASIC_PACKAGES
        #        if is_installed:
        #            worker = self.api.conda_install(prefix=self.prefix, pkgs=pkgs)
        #        else:
        #            worker = self.api.conda_create(prefix=self.prefix, pkgs=pkgs)

        worker = self.api.conda_install(prefix=self.prefix, pkgs=pkgs)
        worker.sig_finished.connect(self._application_installed)
        worker.sig_partial.connect(self._partial_output_ready)
        self.set_loading(True)
        self.widget.sig_status_updated.emit('Installing application '
                                            '<b>{0}</b>'.format(self.name))

    def launch_application(self):
        """
        Launch application installed in prefix environment.
        """
        if self.command is not None:
            if self.command.startswith('open'):
                command = self.command.replace("${PREFIX}", self.prefix)
            elif self.prefix:
                command = os.sep.join([self.prefix, 'bin', self.command])
            else:
                command = self.command

            self.button_launch.setDisabled(True)
            self.timer.setInterval(self.timeout)
            self.timer.start()
            update_pointer(Qt.BusyCursor)
            launch(self.path, command)

    def remove_application(self):
        """
        Remove the application from the defined prefix environment.
        """
        pkg = self.name
        pkgs = [pkg]
        logger.debug(str((self.name, self.dev_tool)))
        worker = self.api.conda_remove(prefix=self.prefix, pkgs=pkgs)
        worker.sig_finished.connect(self._application_removed)
        worker.sig_partial.connect(self._partial_output_ready)
        self.set_loading(True)
        self.widget.sig_status_updated.emit('Removing application '
                                            '<b>{0}</b>'.format(self.name))

    def update_application(self):
        """
        Update the application on the defined prefix environment.
        """
        logger.debug(str((self.name, self.dev_tool, self.installed)))
        self.install_application(version=self.versions[-1])
        self.widget.sig_status_updated.emit('Updating application '
                                            '<b>{0}</b>'.format(self.name))