예제 #1
0
    def __init__(self, parent, config=CONF):
        """Conda Packages Widget."""
        super(CondaPackagesWidget, self).__init__(parent)

        self._parent = parent
        self._current_model_index = None
        self._current_action_name = ''
        self._current_table_scroll = None
        self._hide_widgets = False

        self.api = AnacondaAPI()
        self.prefix = None

        self.style_sheet = None
        self.message = ''
        self.config = config

        # Widgets
        self.bbox = QDialogButtonBox(Qt.Horizontal)
        self.button_cancel = ButtonPackageCancel('Cancel')
        self.button_channels = ButtonPackageChannels(_('Channels'))
        self.button_ok = ButtonPackageOk(_('Ok'))
        self.button_update = ButtonPackageUpdate(_('Update index...'))
        self.button_apply = ButtonPackageApply(_('Apply'))
        self.button_clear = ButtonPackageClear(_('Clear'))
        self.combobox_filter = ComboBoxPackageFilter(self)
        self.frame_top = FrameTabHeader()
        self.frame_bottom = FrameTabFooter()
        self.progress_bar = ProgressBarPackage(self)
        self.label_status = LabelPackageStatus(self)
        self.label_status_action = LabelPackageStatusAction(self)
        self.table = TableCondaPackages(self)
        self.textbox_search = LineEditSearch(self)
        self.widgets = [
            self.button_update, self.button_channels, self.combobox_filter,
            self.textbox_search, self.table, self.button_ok, self.button_apply,
            self.button_clear
        ]
        self.table_first_row = FirstRowWidget(
            widget_before=self.textbox_search)
        self.table_last_row = LastRowWidget(widgets_after=[
            self.button_apply,
            self.button_clear,
            self.button_cancel,
        ])

        # Widgets setup
        max_height = self.label_status.fontMetrics().height()
        max_width = self.textbox_search.fontMetrics().width('M' * 23)
        self.bbox.addButton(self.button_ok, QDialogButtonBox.ActionRole)
        self.button_ok.setMaximumSize(QSize(0, 0))
        self.button_ok.setVisible(False)
        self.button_channels.setCheckable(True)
        combo_items = [k for k in C.COMBOBOX_VALUES_ORDERED]
        self.combobox_filter.addItems(combo_items)
        self.combobox_filter.setMinimumWidth(120)
        self.progress_bar.setMaximumHeight(max_height * 1.2)
        self.progress_bar.setMaximumWidth(max_height * 12)
        self.progress_bar.setTextVisible(False)
        self.progress_bar.setVisible(False)
        self.setMinimumSize(QSize(480, 300))
        self.setWindowTitle(_("Conda Package Manager"))
        self.label_status.setFixedHeight(max_height * 1.5)
        self.textbox_search.setMaximumWidth(max_width)
        self.textbox_search.setPlaceholderText('Search Packages')
        self.table_first_row.setMaximumHeight(0)
        self.table_last_row.setMaximumHeight(0)
        self.table_last_row.setVisible(False)
        self.table_first_row.setVisible(False)

        # Layout
        top_layout = QHBoxLayout()
        top_layout.addWidget(self.combobox_filter, 0, Qt.AlignCenter)
        top_layout.addWidget(SpacerHorizontal())
        top_layout.addWidget(self.button_channels, 0, Qt.AlignCenter)
        top_layout.addWidget(SpacerHorizontal())
        top_layout.addWidget(self.button_update, 0, Qt.AlignCenter)
        top_layout.addWidget(SpacerHorizontal())
        top_layout.addWidget(self.textbox_search, 0, Qt.AlignCenter)
        top_layout.addStretch()
        self.frame_top.setLayout(top_layout)

        middle_layout = QVBoxLayout()
        middle_layout.addWidget(self.table_first_row)
        middle_layout.addWidget(self.table)
        middle_layout.addWidget(self.table_last_row)

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(self.label_status_action)
        bottom_layout.addWidget(SpacerHorizontal())
        bottom_layout.addWidget(self.label_status)
        bottom_layout.addStretch()
        bottom_layout.addWidget(self.progress_bar)
        bottom_layout.addWidget(SpacerHorizontal())
        bottom_layout.addWidget(self.button_cancel)
        bottom_layout.addWidget(SpacerHorizontal())
        bottom_layout.addWidget(self.button_apply)
        bottom_layout.addWidget(SpacerHorizontal())
        bottom_layout.addWidget(self.button_clear)
        self.frame_bottom.setLayout(bottom_layout)

        layout = QVBoxLayout(self)
        layout.addWidget(self.frame_top)
        layout.addLayout(middle_layout)
        layout.addWidget(self.frame_bottom)
        self.setLayout(layout)

        # Signals and slots
        self.button_cancel.clicked.connect(
            lambda: self.sig_cancel_requested.emit(C.TAB_ENVIRONMENT))
        self.combobox_filter.currentTextChanged.connect(self.filter_package)
        self.button_apply.clicked.connect(self.apply_multiple_actions)
        self.button_clear.clicked.connect(self.clear_actions)
        self.button_channels.clicked.connect(self.show_channels)
        self.button_update.clicked.connect(self.update_package_index)
        self.textbox_search.textChanged.connect(self.search_package)
        self.table.sig_actions_updated.connect(self.update_actions)
        self.table.sig_status_updated.connect(self.update_status)

        self.table.sig_next_focus.connect(self.table_last_row.handle_tab)
        self.table.sig_previous_focus.connect(
            lambda: self.table_first_row.widget_before.setFocus())
        self.table_first_row.sig_enter_first.connect(self._handle_tab_focus)
        self.table_last_row.sig_enter_last.connect(self._handle_backtab_focus)

        self.button_cancel.setVisible(False)
예제 #2
0
    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)
예제 #3
0
class CondaPackagesWidget(QWidget):
    """Conda Packages Widget."""

    sig_ready = Signal()
    sig_next_focus = Signal()

    # conda_packages_action_dict, pip_packages_action_dict
    sig_packages_action_requested = Signal(object, object)

    # button_widget, sender
    sig_channels_requested = Signal(object, object)

    # sender
    sig_update_index_requested = Signal(object)
    sig_cancel_requested = Signal(object)

    def __init__(self, parent, config=CONF):
        """Conda Packages Widget."""
        super(CondaPackagesWidget, self).__init__(parent)

        self._parent = parent
        self._current_model_index = None
        self._current_action_name = ''
        self._current_table_scroll = None
        self._hide_widgets = False

        self.api = AnacondaAPI()
        self.prefix = None

        self.style_sheet = None
        self.message = ''
        self.config = config

        # Widgets
        self.bbox = QDialogButtonBox(Qt.Horizontal)
        self.button_cancel = ButtonPackageCancel('Cancel')
        self.button_channels = ButtonPackageChannels(_('Channels'))
        self.button_ok = ButtonPackageOk(_('Ok'))
        self.button_update = ButtonPackageUpdate(_('Update index...'))
        self.button_apply = ButtonPackageApply(_('Apply'))
        self.button_clear = ButtonPackageClear(_('Clear'))
        self.combobox_filter = ComboBoxPackageFilter(self)
        self.frame_top = FrameTabHeader()
        self.frame_bottom = FrameTabFooter()
        self.progress_bar = ProgressBarPackage(self)
        self.label_status = LabelPackageStatus(self)
        self.label_status_action = LabelPackageStatusAction(self)
        self.table = TableCondaPackages(self)
        self.textbox_search = LineEditSearch(self)
        self.widgets = [
            self.button_update, self.button_channels, self.combobox_filter,
            self.textbox_search, self.table, self.button_ok, self.button_apply,
            self.button_clear
        ]
        self.table_first_row = FirstRowWidget(
            widget_before=self.textbox_search)
        self.table_last_row = LastRowWidget(widgets_after=[
            self.button_apply,
            self.button_clear,
            self.button_cancel,
        ])

        # Widgets setup
        max_height = self.label_status.fontMetrics().height()
        max_width = self.textbox_search.fontMetrics().width('M' * 23)
        self.bbox.addButton(self.button_ok, QDialogButtonBox.ActionRole)
        self.button_ok.setMaximumSize(QSize(0, 0))
        self.button_ok.setVisible(False)
        self.button_channels.setCheckable(True)
        combo_items = [k for k in C.COMBOBOX_VALUES_ORDERED]
        self.combobox_filter.addItems(combo_items)
        self.combobox_filter.setMinimumWidth(120)
        self.progress_bar.setMaximumHeight(max_height * 1.2)
        self.progress_bar.setMaximumWidth(max_height * 12)
        self.progress_bar.setTextVisible(False)
        self.progress_bar.setVisible(False)
        self.setMinimumSize(QSize(480, 300))
        self.setWindowTitle(_("Conda Package Manager"))
        self.label_status.setFixedHeight(max_height * 1.5)
        self.textbox_search.setMaximumWidth(max_width)
        self.textbox_search.setPlaceholderText('Search Packages')
        self.table_first_row.setMaximumHeight(0)
        self.table_last_row.setMaximumHeight(0)
        self.table_last_row.setVisible(False)
        self.table_first_row.setVisible(False)

        # Layout
        top_layout = QHBoxLayout()
        top_layout.addWidget(self.combobox_filter, 0, Qt.AlignCenter)
        top_layout.addWidget(SpacerHorizontal())
        top_layout.addWidget(self.button_channels, 0, Qt.AlignCenter)
        top_layout.addWidget(SpacerHorizontal())
        top_layout.addWidget(self.button_update, 0, Qt.AlignCenter)
        top_layout.addWidget(SpacerHorizontal())
        top_layout.addWidget(self.textbox_search, 0, Qt.AlignCenter)
        top_layout.addStretch()
        self.frame_top.setLayout(top_layout)

        middle_layout = QVBoxLayout()
        middle_layout.addWidget(self.table_first_row)
        middle_layout.addWidget(self.table)
        middle_layout.addWidget(self.table_last_row)

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(self.label_status_action)
        bottom_layout.addWidget(SpacerHorizontal())
        bottom_layout.addWidget(self.label_status)
        bottom_layout.addStretch()
        bottom_layout.addWidget(self.progress_bar)
        bottom_layout.addWidget(SpacerHorizontal())
        bottom_layout.addWidget(self.button_cancel)
        bottom_layout.addWidget(SpacerHorizontal())
        bottom_layout.addWidget(self.button_apply)
        bottom_layout.addWidget(SpacerHorizontal())
        bottom_layout.addWidget(self.button_clear)
        self.frame_bottom.setLayout(bottom_layout)

        layout = QVBoxLayout(self)
        layout.addWidget(self.frame_top)
        layout.addLayout(middle_layout)
        layout.addWidget(self.frame_bottom)
        self.setLayout(layout)

        # Signals and slots
        self.button_cancel.clicked.connect(
            lambda: self.sig_cancel_requested.emit(C.TAB_ENVIRONMENT))
        self.combobox_filter.currentTextChanged.connect(self.filter_package)
        self.button_apply.clicked.connect(self.apply_multiple_actions)
        self.button_clear.clicked.connect(self.clear_actions)
        self.button_channels.clicked.connect(self.show_channels)
        self.button_update.clicked.connect(self.update_package_index)
        self.textbox_search.textChanged.connect(self.search_package)
        self.table.sig_actions_updated.connect(self.update_actions)
        self.table.sig_status_updated.connect(self.update_status)

        self.table.sig_next_focus.connect(self.table_last_row.handle_tab)
        self.table.sig_previous_focus.connect(
            lambda: self.table_first_row.widget_before.setFocus())
        self.table_first_row.sig_enter_first.connect(self._handle_tab_focus)
        self.table_last_row.sig_enter_last.connect(self._handle_backtab_focus)

        self.button_cancel.setVisible(False)

    # --- Helpers
    # -------------------------------------------------------------------------
    def _handle_tab_focus(self):
        self.table.setFocus()
        if self.table.proxy_model:
            index = self.table.proxy_model.index(0, 0)
            self.table.setCurrentIndex(index)

    def _handle_backtab_focus(self):
        self.table.setFocus()
        if self.table.proxy_model:
            row = self.table.proxy_model.rowCount() - 1
            index = self.table.proxy_model.index(row, 0)
            self.table.setCurrentIndex(index)

    # --- Setup
    # -------------------------------------------------------------------------
    def setup(self, packages=None, model_data=None, prefix=None):
        """
        Setup packages.

        Populate the table with `packages` information.

        Parameters
        ----------
        packages: dict
            Grouped package information by package name.
        blacklist: list of str
            List of conda package names to be excluded from the actual package
            manager view.
        """
        self.table.setup_model(packages, model_data)
        combobox_text = self.combobox_filter.currentText()
        self.combobox_filter.setCurrentText(combobox_text)
        self.filter_package(combobox_text)
        self.table.setFocus()
        self.sig_ready.emit()

    # --- Other methods
    # -------------------------------------------------------------------------
    def apply_multiple_actions(self):
        """Apply multiple actions on packages."""
        logger.debug('')

        actions = self.table.get_actions()
        pip_actions = actions[C.PIP_PACKAGE]
        conda_actions = actions[C.CONDA_PACKAGE]

        self.sig_packages_action_requested.emit(conda_actions, pip_actions)

    def clear_actions(self):
        """Clear the table actions."""
        self.table.clear_actions()

    def filter_package(self, value):
        """Filter packages by type."""
        self.table.filter_status_changed(value)

    def search_package(self, text):
        """Search and filter packages by text."""
        self.table.search_string_changed(text)

    def show_channels(self):
        """Show channel dialog."""
        self.sig_channels_requested.emit(
            self.button_channels,
            C.TAB_ENVIRONMENT,
        )

    def update_actions(self, number_of_actions):
        """Update visibility of buttons based on actions."""
        self.button_apply.setVisible(bool(number_of_actions))
        self.button_clear.setVisible(bool(number_of_actions))

    def update_package_index(self):
        """Update pacakge index."""
        self.sig_update_index_requested.emit(C.ENVIRONMENT_PACKAGE_MANAGER)

    # --- Common methods
    # -------------------------------------------------------------------------
    def ordered_widgets(self):
        pass

    def set_widgets_enabled(self, value):
        """Set the enabled status of widgets and subwidgets."""
        self.table.setEnabled(value)
        self.button_clear.setEnabled(value)
        self.button_apply.setEnabled(value)
        self.button_cancel.setEnabled(not value)
        self.button_cancel.setVisible(not value)

    def update_status(self, action='', message='', value=None, max_value=None):
        """
        Update status of package widget.

        - progress == None and max_value == None -> Not Visible
        - progress == 0 and max_value == 0 -> Busy
        - progress == n and max_value == m -> Progress values
        """
        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.setStyleSheet(self.style_sheet)
예제 #4
0
    def __init__(self, parent=None):
        super(ProjectsTab, self).__init__(parent)

        # Variables
        self.api = AnacondaAPI()
        self.current_project = None
        self.style_sheet = None
        self.projects = None

        # Widgets
        self.frame_list = FrameEnvironmentsList(self)
        self.frame_widget = FrameEnvironmentsPackages(self)
        self.frame_header_left = FrameTabHeader()
        self.frame_header_right = FrameTabHeader()
        self.button_create = ButtonToolNormal(text="Create")
        self.button_import = ButtonToolNormal(text="Import")
        self.button_remove = ButtonToolNormal(text="Remove")
        self.button_toggle_collapse = ButtonToggleCollapse()
        self.list = ListWidgetEnv()
        self.widget = ProjectsWidget()
        self.menu_list = QMenu()
        self.text_search = LineEditSearch()

        # Widgets setup
        self.frame_list.is_expanded = True
        self.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.text_search.setPlaceholderText("Search Projects")
        self.button_create.setObjectName("create")
        self.button_import.setObjectName("import")
        self.button_remove.setObjectName("remove")

        # Layouts
        layout_header_left = QVBoxLayout()
        layout_header_left.addWidget(self.text_search)
        self.frame_header_left.setLayout(layout_header_left)

        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.button_create)
        layout_buttons.addWidget(self.button_import)
        layout_buttons.addWidget(self.button_remove)

        layout_list_buttons = QVBoxLayout()
        layout_list_buttons.addWidget(self.frame_header_left)
        layout_list_buttons.addWidget(self.list)
        layout_list_buttons.addLayout(layout_buttons)
        self.frame_list.setLayout(layout_list_buttons)

        layout_widget = QHBoxLayout()
        layout_widget.addWidget(self.widget)
        self.frame_widget.setLayout(layout_widget)

        layout_main = QHBoxLayout()
        layout_main.addWidget(self.frame_list, 10)
        layout_main.addWidget(self.button_toggle_collapse, 1)
        layout_main.addWidget(self.frame_widget, 30)
        self.setLayout(layout_main)

        # Signals
        self.button_toggle_collapse.clicked.connect(self.expand_collapse)
        self.button_create.clicked.connect(self.sig_create_requested)
        self.button_import.clicked.connect(self.sig_import_requested)
        self.button_remove.clicked.connect(self.sig_remove_requested)

        self.list.sig_item_selected.connect(self._item_selected)
        self.text_search.textChanged.connect(self.filter_list)
        self.widget.sig_login_requested.connect(self.sig_login_requested)

        self.refresh()
예제 #5
0
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)
예제 #6
0
class ProjectsTab(WidgetBase):
    """Projects management tab."""
    # name, path, sender
    sig_item_selected = Signal(object, object, object)

    sig_create_requested = Signal()
    sig_import_requested = Signal()
    sig_remove_requested = Signal()
    sig_upload_requested = Signal()

    sig_login_requested = Signal()
    sig_ready = Signal()

    #    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)

        # Variables
        self.api = AnacondaAPI()
        self.current_project = None
        self.style_sheet = None
        self.projects = None

        # Widgets
        self.frame_list = FrameEnvironmentsList(self)
        self.frame_widget = FrameEnvironmentsPackages(self)
        self.frame_header_left = FrameTabHeader()
        self.frame_header_right = FrameTabHeader()
        self.button_create = ButtonToolNormal(text="Create")
        self.button_import = ButtonToolNormal(text="Import")
        self.button_remove = ButtonToolNormal(text="Remove")
        self.button_toggle_collapse = ButtonToggleCollapse()
        self.list = ListWidgetEnv()
        self.widget = ProjectsWidget()
        self.menu_list = QMenu()
        self.text_search = LineEditSearch()

        # Widgets setup
        self.frame_list.is_expanded = True
        self.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.text_search.setPlaceholderText("Search Projects")
        self.button_create.setObjectName("create")
        self.button_import.setObjectName("import")
        self.button_remove.setObjectName("remove")

        # Layouts
        layout_header_left = QVBoxLayout()
        layout_header_left.addWidget(self.text_search)
        self.frame_header_left.setLayout(layout_header_left)

        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.button_create)
        layout_buttons.addWidget(self.button_import)
        layout_buttons.addWidget(self.button_remove)

        layout_list_buttons = QVBoxLayout()
        layout_list_buttons.addWidget(self.frame_header_left)
        layout_list_buttons.addWidget(self.list)
        layout_list_buttons.addLayout(layout_buttons)
        self.frame_list.setLayout(layout_list_buttons)

        layout_widget = QHBoxLayout()
        layout_widget.addWidget(self.widget)
        self.frame_widget.setLayout(layout_widget)

        layout_main = QHBoxLayout()
        layout_main.addWidget(self.frame_list, 10)
        layout_main.addWidget(self.button_toggle_collapse, 1)
        layout_main.addWidget(self.frame_widget, 30)
        self.setLayout(layout_main)

        # Signals
        self.button_toggle_collapse.clicked.connect(self.expand_collapse)
        self.button_create.clicked.connect(self.sig_create_requested)
        self.button_import.clicked.connect(self.sig_import_requested)
        self.button_remove.clicked.connect(self.sig_remove_requested)

        self.list.sig_item_selected.connect(self._item_selected)
        self.text_search.textChanged.connect(self.filter_list)
        self.widget.sig_login_requested.connect(self.sig_login_requested)

        self.refresh()

    # --- Setup methods
    # -------------------------------------------------------------------------

    def setup(self, projects):
        """Setup tab content and populates the list of projects."""
        self.set_projects(projects=projects)

    def set_projects(self, projects, current_project=None):
        """Populate the list of projects."""
        self.projects = projects
        if current_project is None:
            for (proj_path, proj_name) in projects.items():
                current_project = proj_path
                break

        self.list.clear()
        self.current_project = current_project
        selected_item_row = 0
        for i, (proj_path, proj_name) in enumerate(projects.items()):
            item = ListItemEnv(prefix=proj_path, name=proj_name)

            if proj_path == self.current_project:
                selected_item_row = i
            self.list.addItem(item)

        loading = False
        self.list.setCurrentRow(selected_item_row, loading=loading)
        self.set_project_widget(self.current_project)
        self.filter_list()

    def set_project_widget(self, project_path):
        """Set the project widget."""
        if project_path is None:
            # Disabled widget
            pass
        else:
            self.widget.load_project(project_path)
        self.refresh()
        self.sig_ready.emit()

    def before_delete(self):
        """Prerpare widget before delete."""
        self.widget.before_delete()

    def update_brand(self, brand):
        """Update service brand."""
        self.widget.update_brand(brand)

    # --- Common Helpers (# FIXME: factor out to common base widget)
    # -------------------------------------------------------------------------
    def _item_selected(self, item):
        """Callback to emit signal as user selects an item from the list."""
        prefix = item.prefix()
        self.current_project = prefix
        self.set_loading(prefix)
        self.sig_item_selected.emit(item.name, prefix, C.TAB_PROJECTS)

    def add_temporal_item(self, name):
        """Creates a temporal item on list while creation becomes effective."""
        item_names = [item.name for item in self.list.items()]
        item_names.append(name)
        index = list(sorted(item_names)).index(name) + 1
        item = ListItemEnv(name=name)
        self.list.insertItem(index, item)
        self.list.setCurrentRow(index)
        self.list.scrollToItem(item)
        item.set_loading(True)

    def expand_collapse(self):
        """Expand or collapse the list selector."""
        if self.frame_list.is_expanded:
            self.frame_list.hide()
            self.frame_list.is_expanded = False
        else:
            self.frame_list.show()
            self.frame_list.is_expanded = True

    def filter_list(self, text=None):
        """Filter items in list by name."""
        text = self.text_search.text().lower()
        for i in range(self.list.count()):
            item = self.list.item(i)
            item.setHidden(text not in item.name.lower())

            if not item.widget.isVisible():
                item.widget.repaint()

    def ordered_widgets(self, next_widget=None):
        """Return a list of the ordered widgets."""
        ordered_widgets = [self.text_search]
        ordered_widgets += self.list.ordered_widgets()
        ordered_widgets += [
            self.button_create, self.button_import, self.button_remove
        ]
        ordered_widgets += self.widget.ordered_widgets()
        return ordered_widgets

    def refresh(self):
        """Refresh the enabled/disabled status of the widget and subwidgets."""
        projects = self.projects
        active = bool(projects)
        if not active:
            self.widget.clear()
        self.widget.setVisible(active)
        self.button_remove.setEnabled(active)
        self.widget.setEnabled(active)

    def set_loading(self, prefix=None, value=True):
        """Set the item given by `prefix` to loading state."""
        for row, item in enumerate(self.list.items()):
            if item.prefix == prefix:
                item.set_loading(value)
                self.list.setCurrentRow(row)
                break

    def set_widgets_enabled(self, value):
        """Change the enabled status of widgets and subwidgets."""
        self.list.setEnabled(value)
        self.button_create.setEnabled(value)
        self.button_import.setEnabled(value)
        self.button_remove.setEnabled(value)
        self.widget_projects.set_widgets_enabled(value)
        if value:
            self.refresh()

    @staticmethod
    def update_status(action=None, message=None, value=0, max_value=0):
        """Update status bar."""
        # TODO:!

    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.setStyleSheet(self.style_sheet)
예제 #7
0
    def __init__(self, parent=None):
        """Conda environments tab."""
        super(EnvironmentsTab, self).__init__(parent)

        # Variables
        self.api = AnacondaAPI()
        self.current_prefix = None
        self.style_sheet = None

        # Widgets
        self.frame_header_left = FrameTabHeader()
        self.frame_list = FrameEnvironmentsList(self)
        self.frame_widget = FrameEnvironmentsPackages(self)
        self.text_search = LineEditSearch()
        self.list = ListWidgetEnv()
        self.menu_list = QMenu()
        self.button_create = ButtonToolNormal(text="Create")
        self.button_clone = ButtonToolNormal(text="Clone")
        self.button_import = ButtonToolNormal(text="Import")
        self.button_remove = ButtonToolNormal(text="Remove")
        self.button_toggle_collapse = ButtonToggleCollapse()
        self.widget = CondaPackagesWidget(parent=self)

        # Widgets setup
        self.frame_list.is_expanded = True
        self.text_search.setPlaceholderText("Search Environments")
        self.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.button_create.setObjectName("create")  # Needed for QSS selectors
        self.button_clone.setObjectName("clone")
        self.button_import.setObjectName("import")
        self.button_remove.setObjectName("remove")
        self.widget.textbox_search.set_icon_visibility(False)

        # Layouts
        layout_header_left = QVBoxLayout()
        layout_header_left.addWidget(self.text_search)
        self.frame_header_left.setLayout(layout_header_left)

        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.button_create)
        layout_buttons.addWidget(self.button_clone)
        layout_buttons.addWidget(self.button_import)
        layout_buttons.addWidget(self.button_remove)

        layout_list_buttons = QVBoxLayout()
        layout_list_buttons.addWidget(self.frame_header_left)
        layout_list_buttons.addWidget(self.list)
        layout_list_buttons.addLayout(layout_buttons)
        self.frame_list.setLayout(layout_list_buttons)

        layout_widget = QHBoxLayout()
        layout_widget.addWidget(self.widget)
        self.frame_widget.setLayout(layout_widget)

        layout_main = QHBoxLayout()
        layout_main.addWidget(self.frame_list, 10)
        layout_main.addWidget(self.button_toggle_collapse, 1)
        layout_main.addWidget(self.frame_widget, 30)

        self.setLayout(layout_main)

        # Signals for buttons and boxes
        self.button_toggle_collapse.clicked.connect(self.expand_collapse)
        self.button_create.clicked.connect(self.sig_create_requested)
        self.button_clone.clicked.connect(self.sig_clone_requested)
        self.button_import.clicked.connect(self.sig_import_requested)
        self.button_remove.clicked.connect(self.sig_remove_requested)
        self.text_search.textChanged.connect(self.filter_list)

        # Signals for list
        self.list.sig_item_selected.connect(self._item_selected)

        # Signals for packages widget
        self.widget.sig_ready.connect(self.sig_ready)
        self.widget.sig_channels_requested.connect(self.sig_channels_requested)
        self.widget.sig_update_index_requested.connect(
            self.sig_update_index_requested)
        self.widget.sig_cancel_requested.connect(self.sig_cancel_requested)
        self.widget.sig_packages_action_requested.connect(
            self.sig_packages_action_requested)
예제 #8
0
class EnvironmentsTab(WidgetBase):
    """Conda environments tab."""
    BLACKLIST = ['anaconda-navigator', '_license']  # Hide in package manager

    # --- Signals
    # -------------------------------------------------------------------------
    sig_ready = Signal()

    # name, prefix, sender
    sig_item_selected = Signal(object, object, object)

    # sender, func_after_dlg_accept, func_callback_on_finished
    sig_create_requested = Signal()
    sig_clone_requested = Signal()
    sig_import_requested = Signal()
    sig_remove_requested = Signal()

    # button_widget, sender_constant
    sig_channels_requested = Signal(object, object)

    # sender_constant
    sig_update_index_requested = Signal(object)
    sig_cancel_requested = Signal(object)

    # conda_packages_action_dict, pip_packages_action_dict
    sig_packages_action_requested = Signal(object, object)

    def __init__(self, parent=None):
        """Conda environments tab."""
        super(EnvironmentsTab, self).__init__(parent)

        # Variables
        self.api = AnacondaAPI()
        self.current_prefix = None
        self.style_sheet = None

        # Widgets
        self.frame_header_left = FrameTabHeader()
        self.frame_list = FrameEnvironmentsList(self)
        self.frame_widget = FrameEnvironmentsPackages(self)
        self.text_search = LineEditSearch()
        self.list = ListWidgetEnv()
        self.menu_list = QMenu()
        self.button_create = ButtonToolNormal(text="Create")
        self.button_clone = ButtonToolNormal(text="Clone")
        self.button_import = ButtonToolNormal(text="Import")
        self.button_remove = ButtonToolNormal(text="Remove")
        self.button_toggle_collapse = ButtonToggleCollapse()
        self.widget = CondaPackagesWidget(parent=self)

        # Widgets setup
        self.frame_list.is_expanded = True
        self.text_search.setPlaceholderText("Search Environments")
        self.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.button_create.setObjectName("create")  # Needed for QSS selectors
        self.button_clone.setObjectName("clone")
        self.button_import.setObjectName("import")
        self.button_remove.setObjectName("remove")
        self.widget.textbox_search.set_icon_visibility(False)

        # Layouts
        layout_header_left = QVBoxLayout()
        layout_header_left.addWidget(self.text_search)
        self.frame_header_left.setLayout(layout_header_left)

        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.button_create)
        layout_buttons.addWidget(self.button_clone)
        layout_buttons.addWidget(self.button_import)
        layout_buttons.addWidget(self.button_remove)

        layout_list_buttons = QVBoxLayout()
        layout_list_buttons.addWidget(self.frame_header_left)
        layout_list_buttons.addWidget(self.list)
        layout_list_buttons.addLayout(layout_buttons)
        self.frame_list.setLayout(layout_list_buttons)

        layout_widget = QHBoxLayout()
        layout_widget.addWidget(self.widget)
        self.frame_widget.setLayout(layout_widget)

        layout_main = QHBoxLayout()
        layout_main.addWidget(self.frame_list, 10)
        layout_main.addWidget(self.button_toggle_collapse, 1)
        layout_main.addWidget(self.frame_widget, 30)

        self.setLayout(layout_main)

        # Signals for buttons and boxes
        self.button_toggle_collapse.clicked.connect(self.expand_collapse)
        self.button_create.clicked.connect(self.sig_create_requested)
        self.button_clone.clicked.connect(self.sig_clone_requested)
        self.button_import.clicked.connect(self.sig_import_requested)
        self.button_remove.clicked.connect(self.sig_remove_requested)
        self.text_search.textChanged.connect(self.filter_list)

        # Signals for list
        self.list.sig_item_selected.connect(self._item_selected)

        # Signals for packages widget
        self.widget.sig_ready.connect(self.sig_ready)
        self.widget.sig_channels_requested.connect(self.sig_channels_requested)
        self.widget.sig_update_index_requested.connect(
            self.sig_update_index_requested)
        self.widget.sig_cancel_requested.connect(self.sig_cancel_requested)
        self.widget.sig_packages_action_requested.connect(
            self.sig_packages_action_requested)

    # --- Setup methods
    # -------------------------------------------------------------------------
    def setup(self, conda_data):
        """Setup tab content and populates the list of environments."""
        self.set_widgets_enabled(False)
        conda_processed_info = conda_data.get('processed_info')
        environments = conda_processed_info.get('__environments')
        packages = conda_data.get('packages')
        self.current_prefix = conda_processed_info.get('default_prefix')
        self.set_environments(environments)
        self.set_packages(packages)

    def set_environments(self, environments):
        """Populate the list of environments."""
        self.list.clear()
        selected_item_row = 0
        for i, (env_prefix, env_name) in enumerate(environments.items()):
            item = ListItemEnv(prefix=env_prefix, name=env_name)
            item.button_options.clicked.connect(self.show_environment_menu)
            if env_prefix == self.current_prefix:
                selected_item_row = i
            self.list.addItem(item)

        self.list.setCurrentRow(selected_item_row, loading=True)
        self.filter_list()

    def _set_packages(self, worker, output, error):
        """Set packages callback."""
        packages, model_data = output
        self.widget.setup(packages, model_data)
        self.set_widgets_enabled(True)
        self.set_loading(prefix=self.current_prefix, value=False)

    def set_packages(self, packages):
        """Set packages widget content."""
        worker = self.api.process_packages(packages,
                                           prefix=self.current_prefix,
                                           blacklist=self.BLACKLIST)
        worker.sig_chain_finished.connect(self._set_packages)

    def show_environment_menu(self, value=None, position=None):
        """Show the environment actions menu."""
        self.menu_list.clear()
        menu_item = self.menu_list.addAction('Open Terminal')
        menu_item.triggered.connect(
            lambda: self.open_environment_in('terminal'))

        for word in ['Python', 'IPython', 'Jupyter Notebook']:
            menu_item = self.menu_list.addAction("Open with " + word)
            menu_item.triggered.connect(
                lambda x, w=word: self.open_environment_in(w.lower()))

        current_item = self.list.currentItem()
        prefix = current_item.prefix

        if isinstance(position, bool) or position is None:
            width = current_item.button_options.width()
            position = QPoint(width, 0)

        point = QPoint(0, 0)
        parent_position = current_item.button_options.mapToGlobal(point)
        self.menu_list.move(parent_position + position)

        # Disabled actions depending on the environment installed packages
        actions = self.menu_list.actions()
        actions[2].setEnabled(launch.check_prog('ipython', prefix))
        actions[3].setEnabled(launch.check_prog('notebook', prefix))

        self.menu_list.exec_()

    def open_environment_in(self, which):
        """Open selected environment in console terminal."""
        prefix = self.list.currentItem().prefix
        logger.debug("%s, %s", which, prefix)

        if which == 'terminal':
            launch.console(prefix)
        else:
            launch.py_in_console(prefix, which)

    # --- Common Helpers (# FIXME: factor out to common base widget)
    # -------------------------------------------------------------------------
    def _item_selected(self, item):
        """Callback to emit signal as user selects an item from the list."""
        self.set_loading(prefix=item.prefix)
        self.sig_item_selected.emit(item.name, item.prefix, C.TAB_ENVIRONMENT)

    def add_temporal_item(self, name):
        """Creates a temporal item on list while creation becomes effective."""
        item_names = [item.name for item in self.list.items()]
        item_names.append(name)
        index = list(sorted(item_names)).index(name) + 1
        item = ListItemEnv(name=name)
        self.list.insertItem(index, item)
        self.list.setCurrentRow(index)
        self.list.scrollToItem(item)
        item.set_loading(True)

    def expand_collapse(self):
        """Expand or collapse the list selector."""
        if self.frame_list.is_expanded:
            self.frame_list.hide()
            self.frame_list.is_expanded = False
        else:
            self.frame_list.show()
            self.frame_list.is_expanded = True

    def filter_list(self, text=None):
        """Filter items in list by name."""
        text = self.text_search.text().lower()
        for i in range(self.list.count()):
            item = self.list.item(i)
            item.setHidden(text not in item.name.lower())

            if not item.widget.isVisible():
                item.widget.repaint()

    def ordered_widgets(self, next_widget=None):
        """Return a list of the ordered widgets."""
        if next_widget is not None:
            self.widget.table_last_row.add_focus_widget(next_widget)

        ordered_widgets = [
            self.text_search,
        ]
        ordered_widgets += self.list.ordered_widgets()
        ordered_widgets += [
            self.button_create,
            self.button_clone,
            self.button_import,
            self.button_remove,
            self.widget.combobox_filter,
            self.widget.button_channels,
            self.widget.button_update,
            self.widget.textbox_search,
            # self.widget.table_first_row,
            self.widget.table,
            self.widget.table_last_row,
            self.widget.button_apply,
            self.widget.button_clear,
            self.widget.button_cancel,
        ]
        return ordered_widgets

    def refresh(self):
        """Refresh the enabled/disabled status of the widget and subwidgets."""
        is_root = self.current_prefix == self.api.ROOT_PREFIX
        self.button_clone.setDisabled(is_root)
        self.button_remove.setDisabled(is_root)

    def set_loading(self, prefix=None, value=True):
        """Set the item given by `prefix` to loading state."""
        for row, item in enumerate(self.list.items()):
            if item.prefix == prefix:
                item.set_loading(value)
                self.list.setCurrentRow(row)
                break

    def set_widgets_enabled(self, value):
        """Change the enabled status of widgets and subwidgets."""
        self.list.setEnabled(value)
        self.button_create.setEnabled(value)
        self.button_clone.setEnabled(value)
        self.button_import.setEnabled(value)
        self.button_remove.setEnabled(value)
        self.widget.set_widgets_enabled(value)
        if value:
            self.refresh()

    def update_status(self, action='', message='', value=None, max_value=None):
        """Update widget status and progress bar."""
        self.widget.update_status(action=action,
                                  message=message,
                                  value=value,
                                  max_value=max_value)

    def update_style_sheet(self, style_sheet=None):
        """Update custom CSS stylesheet."""
        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)
        self.menu_list.setStyleSheet(self.style_sheet)
예제 #9
0
파일: community.py 프로젝트: amccarty/ac0n3
    def __init__(self,
                 parent=None,
                 tags=None,
                 content_urls=None,
                 content_path=CONTENT_PATH,
                 image_path=IMAGE_DATA_PATH,
                 config=CONF,
                 bundle_path=LINKS_INFO_PATH,
                 saved_content_path=CONTENT_JSON_PATH,
                 tab_name=''):
        """Community tab."""
        super(CommunityTab, self).__init__(parent=parent)

        self._tab_name = ''
        self.content_path = content_path
        self.image_path = image_path
        self.bundle_path = bundle_path
        self.saved_content_path = saved_content_path
        self.config = config

        self._parent = parent
        self._downloaded_thumbnail_urls = []
        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.filter_widgets = []
        self.default_pixmap = QPixmap(VIDEO_ICON_PATH).scaled(
            100, 60, Qt.KeepAspectRatio, Qt.FastTransformation)

        # Widgets
        self.text_filter = LineEditSearch()
        self.list = ListWidgetContent()
        self.frame_header = FrameTabHeader()
        self.frame_content = FrameTabContent()

        # 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
        self.filters_layout = QHBoxLayout()

        layout_header = QHBoxLayout()
        layout_header.addLayout(self.filters_layout)
        layout_header.addStretch()
        layout_header.addWidget(self.text_filter)
        self.frame_header.setLayout(layout_header)

        layout_content = QHBoxLayout()
        layout_content.addWidget(self.list)
        self.frame_content.setLayout(layout_content)

        layout = QVBoxLayout()
        layout.addWidget(self.frame_header)
        layout.addWidget(self.frame_content)
        self.setLayout(layout)

        # Signals
        self.timer_load.timeout.connect(self.set_content_list)
        self.text_filter.textChanged.connect(self.filter_content)
예제 #10
0
파일: community.py 프로젝트: amccarty/ac0n3
class CommunityTab(WidgetBase):
    """Community tab."""
    # Qt Signals
    sig_video_started = Signal(str, int)
    sig_status_updated = Signal(object, int, int, int)
    sig_ready = Signal(object)  # Sender

    # Class variables
    instances = []

    # Maximum item count for different content type
    VIDEOS_LIMIT = 25
    WEBINARS_LIMIT = 25
    EVENTS_LIMIT = 25

    # Google analytics campaigns
    UTM_MEDIUM = 'in-app'
    UTM_SOURCE = 'navigator'

    def __init__(self,
                 parent=None,
                 tags=None,
                 content_urls=None,
                 content_path=CONTENT_PATH,
                 image_path=IMAGE_DATA_PATH,
                 config=CONF,
                 bundle_path=LINKS_INFO_PATH,
                 saved_content_path=CONTENT_JSON_PATH,
                 tab_name=''):
        """Community tab."""
        super(CommunityTab, self).__init__(parent=parent)

        self._tab_name = ''
        self.content_path = content_path
        self.image_path = image_path
        self.bundle_path = bundle_path
        self.saved_content_path = saved_content_path
        self.config = config

        self._parent = parent
        self._downloaded_thumbnail_urls = []
        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.filter_widgets = []
        self.default_pixmap = QPixmap(VIDEO_ICON_PATH).scaled(
            100, 60, Qt.KeepAspectRatio, Qt.FastTransformation)

        # Widgets
        self.text_filter = LineEditSearch()
        self.list = ListWidgetContent()
        self.frame_header = FrameTabHeader()
        self.frame_content = FrameTabContent()

        # 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
        self.filters_layout = QHBoxLayout()

        layout_header = QHBoxLayout()
        layout_header.addLayout(self.filters_layout)
        layout_header.addStretch()
        layout_header.addWidget(self.text_filter)
        self.frame_header.setLayout(layout_header)

        layout_content = QHBoxLayout()
        layout_content.addWidget(self.list)
        self.frame_content.setLayout(layout_content)

        layout = QVBoxLayout()
        layout.addWidget(self.frame_header)
        layout.addWidget(self.frame_content)
        self.setLayout(layout)

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

    def setup(self):
        """Setup tab content."""
        self.download_content()

    def _json_downloaded(self, worker, output, error):
        """Callbacl for download_content."""
        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):
        """Download content to display in cards."""
        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([self.content_path, fname])
                self._downloaded_urls.append(url)
                self._downloaded_filepaths.append(filepath)
                worker = self.api.download(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:
                    try:
                        item['tags'] = ['video']
                        item['uri'] = item.get('video', '')

                        if item['uri']:
                            item['banner'] = item.get('thumbnail')
                            image_path = item['banner'].split('/')[-1]
                            item['image_file'] = image_path
                        else:
                            url = ''
                            item['image_file'] = ''

                        item['banner'] = url
                        item['date'] = item.get('date_start', '')
                    except Exception:
                        logger.debug("Video parse failed: {0}".format(item))
                items = items[:self.VIDEOS_LIMIT]

            elif 'event' in fname:
                for item in items:
                    try:
                        item['tags'] = ['event']
                        item['uri'] = item.get('url', '')
                        if item['banner']:
                            image_path = item['banner'].split('/')[-1]
                            item['image_file'] = image_path
                        else:
                            item['banner'] = ''
                    except Exception:
                        logger.debug('Event parse failed: {0}'.format(item))
                items = items[:self.EVENTS_LIMIT]

            elif 'webinar' in fname:
                for item in items:
                    try:
                        item['tags'] = ['webinar']
                        uri = item.get('url', '')
                        utm_campaign = item.get('utm_campaign', '')
                        item['uri'] = self.add_campaign(uri, utm_campaign)
                        image = item.get('image', '')

                        if image and isinstance(image, dict):
                            item['banner'] = image.get('src', '')
                            if item['banner']:
                                image_path = item['banner'].split('/')[-1]
                                item['image_file'] = image_path
                            else:
                                item['image_file'] = ''
                        else:
                            item['banner'] = ''
                            item['image_file_path'] = ''
                    except Exception:
                        logger.debug('Webinar parse failed: {0}'.format(item))
                items = items[:self.WEBINARS_LIMIT]

            if items:
                content.extend(items)

        # Load bundled content
        with open(self.bundle_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 image path to get the full path
        for i, item in enumerate(content):
            uri = item['uri']
            item['uri'] = uri.replace(' ', '%20')
            filename = item.get('image_file', '')
            item['image_file_path'] = os.path.sep.join(
                [self.image_path, filename])

            # if 'video' in item['tags']:
            #     print(i, item['uri'])
            #     print(item['banner'])
            #     print(item['image_file_path'])
            #     print('')

        # Make sure items of the same type/tag are contiguous in the list
        content = sorted(content, key=lambda i: i.get('tags'))

        # But also make sure sticky content appears first
        sticky_content = []
        for i, item in enumerate(content[:]):
            sticky = item.get('sticky')
            if isinstance(sticky, str):
                is_sticky = sticky == 'true'
            elif sticky is None:
                is_sticky = False

            # print(i, sticky, is_sticky, item.get('title'))
            if is_sticky:
                sticky_content.append(item)
                content.remove(item)

        content = sticky_content + content
        self.content_info = content

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

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

    def add_campaign(self, uri, utm_campaign):
        """Add tracking analytics campaing to url in content items."""
        if uri and utm_campaign:
            parameters = parse.urlencode({
                'utm_source': self.UTM_SOURCE,
                'utm_medium': self.UTM_MEDIUM,
                'utm_campaign': utm_campaign
            })
            uri = '{0}?{1}'.format(uri, parameters)
        return uri

    def make_tag_filters(self):
        """Create tag filtering checkboxes based on available content tags."""
        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)

        # Get count
        tag_count = {tag: 0 for tag in self.tags}
        for tag in self.tags:
            for content_item in self.content_info:
                item_tags = content_item.get('tags', [])
                if tag in item_tags:
                    tag_count[tag] += 1

        logger.debug("TAGS: {0}".format(self.tags))
        self.filter_widgets = []
        for tag in sorted(self.tags):
            count = tag_count[tag]
            tag_text = "{0} ({1})".format(tag.capitalize(), count).strip()
            item = ButtonToggle(tag_text)
            item.setObjectName(tag.lower())
            item.setChecked(self.config.get('checkboxes', tag.lower(), True))
            item.clicked.connect(self.filter_content)
            self.filter_widgets.append(item)
            self.filters_layout.addWidget(item)
            self.filters_layout.addWidget(SpacerHorizontal())

    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]

        selected_tags = []
        for item in self.filter_widgets:
            tag_parts = item.text().lower().split()
            tag = tag_parts[0]
            # tag_count = tag_parts[-1]

            if item.isChecked():
                selected_tags.append(tag)
                self.config.set('checkboxes', tag, True)
            else:
                self.config.set('checkboxes', tag, False)

        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 selected_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)
                #                self.update_style_sheet(self.style_sheet)

                # This allows the content to look for the pixmap
                content_item.pixmaps = self.pixmaps

                # Use images shipped with Navigator, if no image try the
                # download
                image_file = item.get('image_file', 'NaN')
                local_image = os.path.join(LOGO_PATH, image_file)
                if os.path.isfile(local_image):
                    self.pixmaps[path] = QPixmap(local_image)
                else:
                    self.download_thumbnail(content_item, banner, path)
            else:
                self.timer_load.stop()
                self.sig_ready.emit(self._tab_name)
                break
        self.step += self.step_size
        self.filter_content()

    def download_thumbnail(self, item, url, path):
        """Download all the video thumbnails."""
        # Check url is not an empty string or not already downloaded
        if url and url not in self._downloaded_thumbnail_urls:
            self._downloaded_thumbnail_urls.append(url)
            # For some content the app segfaults (with big files) so
            # we dont use chunks
            worker = self.api.download(url, path, chunked=True)
            worker.url = url
            worker.item = item
            worker.path = path
            worker.sig_finished.connect(self.convert_image)
            logger.debug('Fetching thumbnail {}'.format(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.
        """
        path = output
        if path in self.pixmaps:
            return

        try:
            if sys.platform == 'darwin' and PYQT4:
                from PIL.ImageQt import ImageQt
                from PIL import Image

                if path:
                    image = Image.open(path)
                    image = ImageQt(image)
                    qt_image = QImage(image)
                    pixmap = QPixmap.fromImage(qt_image)
                else:
                    pixmap = QPixmap()
            else:
                if path and os.path.isfile(path):
                    extension = path.split('.')[-1].upper()
                    if extension in ['PNG', 'JPEG', 'JPG']:
                        pixmap = QPixmap(path, format=extension)
                    else:
                        pixmap = QPixmap(path)
                else:
                    pixmap = QPixmap()

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

    def update_style_sheet(self, style_sheet=None):
        """Update custom CSS stylesheet."""
        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)

    def ordered_widgets(self, next_widget=None):
        """Fix tab order of UI widgets."""
        ordered_widgets = []
        ordered_widgets += self.filter_widgets
        ordered_widgets += [self.text_filter]
        ordered_widgets += self.list.ordered_widgets()
        return ordered_widgets