Esempio n. 1
0
def botlicense(qtbot):
    # First remove all licenses
    api = AnacondaAPI()
    api.remove_all_licenses()
    widget = LicenseManagerDialog()
    qtbot.addWidget(widget)
    widget.show()
    return qtbot, widget
Esempio n. 2
0
def run_python_console(project_path, project=None, filename=""):
    """Execute python in env with console."""
    from anaconda_navigator.api import AnacondaAPI
    api = AnacondaAPI()

    if project is None:
        project = api.load_project(project_path)

    cmd = os.sep.join([project.env_prefix(project_path), 'bin', 'python'])
    run_ex(cmd, filename)
Esempio n. 3
0
def run_python_file(project_path, project=None, filename=""):
    """Execute python in environment."""
    from anaconda_navigator.api import AnacondaAPI
    api = AnacondaAPI()

    if project is None:
        project = api.load_project(project_path)

    cmd = os.sep.join([project.env_prefix(project_path), 'bin', 'python'])
    logger.debug(",".join([cmd, filename]))
    subprocess.Popen([cmd, filename])
Esempio n. 4
0
def botlicensefilled(qtbot):
    # First remove all licenses
    api = AnacondaAPI()
    api.remove_all_licenses()
    widget = LicenseManagerDialog()
    qtbot.addWidget(widget)
    widget.show()
    widget.add_license(path=EXPIRED_LICENSE_PATH)
    with qtbot.waitSignal(signal=widget.accepted, timeout=1000, raising=False):
        pass
    return qtbot, widget
Esempio n. 5
0
    def __init__(self):
        """Google analytics tracker."""
        # Avoid blocking the UI
        self._queue = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()
        self.api = AnacondaAPI()

        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

        self.setup()
Esempio n. 6
0
    def __init__(self, config=CONF, **kwargs):
        """Application preferences dialog."""
        super(PreferencesDialog, self).__init__(**kwargs)

        self.api = AnacondaAPI()
        self.widgets_changed = set()
        self.widgets = []
        self.widgets_dic = {}
        self.config = config

        # Widgets
        self.button_ok = ButtonPrimary('Apply')
        self.button_cancel = ButtonNormal('Cancel')
        self.button_reset = ButtonNormal('Reset to defaults')
        self.row = 0

        # Widget setup
        self.setWindowTitle("Preferences")

        # Layouts
        self.grid_layout = QGridLayout()

        buttons_layout = QHBoxLayout()
        buttons_layout.addWidget(self.button_reset)
        buttons_layout.addStretch()
        buttons_layout.addWidget(self.button_cancel)
        buttons_layout.addWidget(SpacerHorizontal())
        buttons_layout.addWidget(self.button_ok)

        main_layout = QVBoxLayout()
        main_layout.addLayout(self.grid_layout)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        main_layout.addLayout(buttons_layout)
        self.setLayout(main_layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.button_reset.clicked.connect(self.reset_to_defaults)
        self.button_reset.clicked.connect(
            lambda: self.button_ok.setEnabled(True)
        )

        # Setup
        self.grid_layout.setSpacing(0)
        self.setup()
        self.button_ok.setDisabled(True)
        self.widgets[0].setFocus()
        self.button_ok.setDefault(True)
        self.button_ok.setAutoDefault(True)
Esempio n. 7
0
def check_prog(prog, prefix=None):
    """Check if program exists in prefix."""
    api = AnacondaAPI()
    prefix = prefix or api.conda_get_prefix_envname(name='root')

    if prog in ['notebook', 'jupyter notebook']:
        pkgs = ['notebook', 'ipython-notebook', 'jupyter-notebook']
    elif prog in ['ipython', 'jupyter console']:
        pkgs = ['ipython', 'jupyter']
    else:
        pkgs = [prog]

    return any(
        api.conda_package_version(prefix=prefix, pkg=p) is not None
        for p in pkgs)
Esempio n. 8
0
def local_test():  # pragma: no cover
    """Run local test."""
    from anaconda_navigator.utils.qthelpers import qapplication
    from anaconda_navigator.utils.styles import load_style_sheet

    app = qapplication()
    api = AnacondaAPI()
    widget = DialogChannels(None)
    widget.update_style_sheet(load_style_sheet())
    widget.show()
    worker = api.conda_config_sources()
    worker_2 = api.api_urls()
    worker.sig_chain_finished.connect(widget.setup)
    worker_2.sig_chain_finished.connect(widget.update_api)
    sys.exit(app.exec_())
Esempio n. 9
0
def run_notebook(project_path, project=None, filename=""):
    """Start notebook server."""
    from anaconda_navigator.api import AnacondaAPI
    api = AnacondaAPI()

    if project is None:
        project = api.load_project(project_path)

    kernels_folder = os.sep.join([os.path.expanduser('~'), ".ipython", "kernels"])
    display_name = '{0} ({1})'.format(project.name, project_path)
    kernel_uuid = uuid.uuid1()
    kernel_path = os.sep.join([kernels_folder, "{name}", "kernel.json"])
    pyexec = get_pyexec(project.env_prefix(project_path))
    spec = {
        "argv": [pyexec, "-m", "IPython.kernel", "-f", "{connection_file}"],
        "display_name": display_name,
        "language": "python",
    }

    # Delete any other kernel sec mathching this name!
    kernels = os.listdir(kernels_folder)
    for kernel in kernels:
        path = os.sep.join([kernels_folder, kernel])
        file_path = os.sep.join([path, 'kernel.json'])

        if os.path.isfile(file_path):
            with open(file_path, 'r') as f:
                data = json.loads(f.read())

            name = data.get('display_name')
            if name is not None and project_path in name:
                shutil.rmtree(path)

    os.makedirs(os.path.split(kernel_path.format(name=kernel_uuid))[0])

    with open(kernel_path.format(name=kernel_uuid), 'w') as f:
        f.write(json.dumps(spec))

    # This is not working!
    cmd = (
        'jupyter notebook '
        '--KernelSpecManager.whitelist={0}'.format(kernel_uuid)
    )

    cmd = ('jupyter notebook')
    command = (cmd + ' ' + filename)
    logger.debug(",".join([command, project_path]))
    subprocess.Popen(command.split(), cwd=project_path)
Esempio n. 10
0
def botchannels(qtbot, tmpfile):
    qtbot.api = AnacondaAPI()
    widget = DialogChannels()
    widget.update_style_sheet(load_style_sheet())

    temp_rc_content = """channels:
  - chdoig
  - sean"""

    with open(tmpfile, 'w') as f:
        f.write(temp_rc_content)

    # Mock conda config data
    config_data = {
        'config_sources': {
            tmpfile: {
                'channels': ['chdoig', 'sean']
            }
        }
    }
    api_info = {'conda_url': 'https://conda.beta.anaconda.org'}
    with qtbot.waitSignal(
        signal=widget.sig_setup_ready, timeout=5000, raising=True
    ):
        if random.randint(0, 1):
            widget.setup(None, config_data, None)
            widget.update_api(None, api_info, None)
        else:
            widget.update_api(None, api_info, None)
            widget.setup(None, config_data, None)

    widget.show()
    return qtbot, widget
Esempio n. 11
0
def tmpjsonfile_production(request):
    """Return a temporal file path with downloaded content."""
    content_base_uri = 'http://anaconda.com/api/{0}?items_per_page=all'
    url = content_base_uri.format(request.param)
    _, path = tempfile.mkstemp(suffix='json', text=True)
    api = AnacondaAPI()
    r = requests.get(url, proxies=api.conda_load_proxy_config())
    with open(path, 'w') as f:
        f.write(r.text)

    def fin():
        print("Finalizing production {0} check".format(request.param))

#        shutil.rmtree(path)

    request.addfinalizer(fin)
    return path
def botlogin(qtbot, tmpconfig):
    tmpconfig.set('main', 'anaconda_api_url', 'https://api.anaconda.org/')
    widget = AuthenticationDialog(api=AnacondaAPI())
    widget.config = tmpconfig  # Patch with a temporal config file
    widget.setup()
    qtbot.addWidget(widget)
    widget.show()
    return qtbot, widget
Esempio n. 13
0
def local_test():  # pragma: no cover
    """Run local test."""
    from anaconda_navigator.utils.qthelpers import qapplication
    app = qapplication(test_time=3)
    load_fonts(app)
    widget = AuthenticationDialog(AnacondaAPI())
    widget.update_style_sheet()
    widget.show()
    app.exec_()
Esempio n. 14
0
def tmpconfig(request):
    """Return a temporal configuration object and delete after used."""
    SUBFOLDER = tempfile.mkdtemp()
    CONF = UserConfig(
        'anaconda-navigator-test',
        defaults=DEFAULTS,
        version=CONF_VERSION,
        subfolder=SUBFOLDER,
        raw_mode=True,
    )
    CONF.reset_to_defaults()
    AnacondaAPI().client_set_api_url('https://api.anaconda.org')

    def fin():
        shutil.rmtree(SUBFOLDER)

    request.addfinalizer(fin)
    return CONF
Esempio n. 15
0
def local_test():  # pragma: no cover
    """Run local tests."""
    from anaconda_navigator.utils.qthelpers import qapplication
    from anaconda_navigator.api.anaconda_api import AnacondaAPI

    app = qapplication()

    api = AnacondaAPI()

    worker = api.info()
    widget_create = CreateDialog(parent=None)
    worker.sig_chain_finished.connect(widget_create.setup)
    widget_create.show()

    worker = api.info()
    widget_import = ImportDialog(parent=None)
    worker.sig_chain_finished.connect(widget_import.setup)
    widget_import.show()

    worker = api.info()
    widget_clone = CloneDialog(parent=None, name='root')
    widget_clone.show()
    worker.sig_chain_finished.connect(widget_clone.setup)

    widget_remove = RemoveDialog(parent=None,
                                 name='root',
                                 prefix='/User/user/anaconda')
    widget_remove.show()

    worker = api.info()
    widget_create_conflict = ConflictDialog(
        parent=None,
        package='orange3',
    )
    worker.sig_chain_finished.connect(widget_create_conflict.setup)
    widget_create_conflict.show()

    app.exec_()
Esempio n. 16
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()
Esempio n. 17
0
class LicenseManagerDialog(DialogBase):
    """License Manager main dialog."""

    CONTACT_LINK = 'https://support.continuum.io/'  # TODO: Centralize this?

    # Url, Sender
    sig_url_clicked = Signal(object, object)

    def __init__(self, parent=None):
        """License Manager main dialog."""
        super(LicenseManagerDialog, self).__init__(parent=parent)

        self.api = AnacondaAPI()

        # Widgets
        self.message_box = None  # For testing
        self.button_add = ButtonPrimary('Add license')
        self.button_ok = ButtonNormal('Close')
        self.button_remove = ButtonNormal('Remove license')
        self.button_contact = ButtonLink('Please contact us.')
        self.label_info = LabelBase('Manage your Continuum Analytics '
                                    'license keys.')
        self.label_contact = LabelBase('Got a problem with your license? ')
        self.proxy_model = QSortFilterProxyModel(parent=self)
        self.model = LicenseModel(parent=self)
        self.table = LicenseTableView(parent=self)
        self.delegate = BackgroundDelegate(self.table)

        # Widget setup
        self.proxy_model.setSourceModel(self.model)
        self.table.setItemDelegate(self.delegate)
        self.table.setModel(self.proxy_model)
        self.setWindowTitle('License Manager')

        # Layouts
        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.label_info)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_add)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_remove)

        layout_buttons_bottom = QHBoxLayout()
        layout_buttons_bottom.addWidget(self.label_contact)
        layout_buttons_bottom.addWidget(self.button_contact)
        layout_buttons_bottom.addStretch()
        layout_buttons_bottom.addWidget(self.button_ok)

        layout = QVBoxLayout()
        layout.addLayout(layout_buttons)
        layout.addWidget(SpacerVertical())
        layout.addWidget(self.table)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_buttons_bottom)
        self.setLayout(layout)

        # Signals
        self.button_add.clicked.connect(lambda: self.add_license())
        self.button_remove.clicked.connect(self.remove_license)
        self.button_ok.clicked.connect(self.accept)
        self.button_contact.clicked.connect(
            lambda v=None: self.sig_url_clicked.emit(self.CONTACT_LINK,
                                                     'License Manager'))
        self.table.sig_dropped.connect(self.handle_drop)

        # Setup
        self.button_add.setFocus()
        self.load_licenses()

    def handle_drop(self, links):
        """Handle a drag and drop event."""
        self.api.add_license(links)
        self.load_licenses()

    def _hide_columns(self):
        """Hide columns."""
        for key, val in COL_MAP.items():
            if val in HIDDEN_COLUMNS:
                self.table.setColumnHidden(key, True)

    def add_license(self, v=None, path=None):
        """Add license file."""
        if path is None:
            filename, selected_filter = getopenfilename(
                self,
                'Select license file',
                filters='License files (*.txt)',
                basedir=get_home_dir(),
            )

            if filename:
                paths = [filename]
            else:
                paths = []
        else:
            paths = [path]

        valid_licenses, invalid_licenses = self.api.add_license(paths)

        for path in invalid_licenses:
            text = ('File: <b>"{0}"</b>'
                    '<br>is not a valid license file.').format(path)
            self.message_box = MessageBoxInformation(
                text=text, title="Invalid license file")
            self.message_box.exec_()

        if valid_licenses:
            self.load_licenses()

    def remove_license(self, row=None):
        """Remove license from file."""
        if row is None:
            index = self.table.currentIndex()
        else:
            index = self.proxy_model.index(row, 0)

        model_index = self.proxy_model.mapToSource(index)
        row_data = self.model.row(model_index.row())

        if row_data:
            text = ('Do you want to remove license for product:<br><br>'
                    '<b>{product}</b> ({issued} - {end_date})')
            text = text.format(product=row_data.get('product'),
                               end_date=row_data.get('end_date'),
                               issued=row_data.get('issued'))
            self.message_box = MessageBoxRemove(title='Remove license',
                                                text=text)
            if self.message_box.exec_():
                self.api.remove_license(row_data)
                self.load_licenses()

    def load_licenses(self):
        """Load license files."""
        res = self.api.load_licenses()
        self.model.load_licenses(res)
        self.proxy_model.setSourceModel(self.model)
        self.table.resizeColumnsToContents()
        self._hide_columns()
        self.update_status()

    def count(self):
        """Return the number of items in the table."""
        return self.table.model().rowCount()

    def update_status(self):
        """Update visible and enabled status for widgets based on actions."""
        self.button_remove.setEnabled(bool(self.count()))
Esempio n. 18
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)
Esempio n. 19
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)
Esempio n. 20
0
class PreferencesDialog(DialogBase):
    """Application preferences dialog."""

    sig_urls_updated = Signal(str, str)
    sig_check_ready = Signal()
    sig_reset_ready = Signal()

    def __init__(self, config=CONF, **kwargs):
        """Application preferences dialog."""
        super(PreferencesDialog, self).__init__(**kwargs)

        self.api = AnacondaAPI()
        self.widgets_changed = set()
        self.widgets = []
        self.widgets_dic = {}
        self.config = config

        # Widgets
        self.button_ok = ButtonPrimary('Apply')
        self.button_cancel = ButtonNormal('Cancel')
        self.button_reset = ButtonNormal('Reset to defaults')
        self.row = 0

        # Widget setup
        self.setWindowTitle("Preferences")

        # Layouts
        self.grid_layout = QGridLayout()

        buttons_layout = QHBoxLayout()
        buttons_layout.addWidget(self.button_reset)
        buttons_layout.addStretch()
        buttons_layout.addWidget(self.button_cancel)
        buttons_layout.addWidget(SpacerHorizontal())
        buttons_layout.addWidget(self.button_ok)

        main_layout = QVBoxLayout()
        main_layout.addLayout(self.grid_layout)
        main_layout.addWidget(SpacerVertical())
        main_layout.addWidget(SpacerVertical())
        main_layout.addLayout(buttons_layout)
        self.setLayout(main_layout)

        # Signals
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.button_reset.clicked.connect(self.reset_to_defaults)
        self.button_reset.clicked.connect(
            lambda: self.button_ok.setEnabled(True)
        )

        # Setup
        self.grid_layout.setSpacing(0)
        self.setup()
        self.button_ok.setDisabled(True)
        self.widgets[0].setFocus()
        self.button_ok.setDefault(True)
        self.button_ok.setAutoDefault(True)

    # --- Helpers
    # -------------------------------------------------------------------------
    def get_option(self, option):
        """Get configuration option from `main` section."""
        return self.config.get('main', option, None)

    def set_option(self, option, value):
        """Set configuration option in `main` section."""
        self.config.set('main', option, value)

    def get_option_default(self, option):
        """Get configuration option default value in `main` section."""
        return self.config.get_default('main', option)

    def set_option_default(self, option):
        """Set configuration option default value in `main` section."""
        self.set_option(option, self.get_option_default(option))

    def create_widget(
        self,
        widget=None,
        label=None,
        option=None,
        hint=None,
        check=None,
        info=None,
    ):
        """Create preference option widget and add to layout."""
        config_value = self.get_option(option)
        widget._text = label
        widget.label = QLabel(label)
        widget.option = option
        widget.set_value(config_value)
        widget.label_information = QLabel()
        widget.label_information.setMinimumWidth(16)
        widget.label_information.setMaximumWidth(16)

        form_widget = QWidget()
        h_layout = QHBoxLayout()
        h_layout.addSpacing(4)
        h_layout.addWidget(widget.label_information, 0, Qt.AlignRight)
        h_layout.addWidget(widget, 0, Qt.AlignLeft)
        h_layout.addWidget(QLabel(hint or ''), 0, Qt.AlignLeft)
        form_widget.setLayout(h_layout)

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

        if info:
            label = widget.label_information
            label = PreferencesDialog.update_icon(label, INFO_ICON)
            label.setToolTip(info)

        self.widgets.append(widget)
        self.widgets_dic[option] = widget
        self.grid_layout.addWidget(
            widget.label, self.row, 0, Qt.AlignRight | Qt.AlignCenter
        )
        self.grid_layout.addWidget(
            form_widget, self.row, 1, Qt.AlignLeft | Qt.AlignCenter
        )
        self.row += 1

    def create_textbox(self, label, option, hint=None, check=None, info=None):
        """Create textbox (QLineEdit) preference option."""
        widget = QLineEdit()
        widget.setAttribute(Qt.WA_MacShowFocusRect, False)
        widget.setMinimumWidth(250)

        widget.get_value = lambda w=widget: w.text()
        widget.set_value = lambda value, w=widget: w.setText(value)
        widget.set_warning = lambda w=widget: w.setSelection(0, 1000)
        widget.textChanged.connect(
            lambda v=None, w=widget: self.options_changed(widget=w)
        )

        self.create_widget(
            widget=widget,
            option=option,
            label=label,
            hint=hint,
            check=check,
            info=info,
        )

    def create_checkbox(self, label, option, check=None, hint=None, info=None):
        """Create checkbox preference option."""
        widget = QCheckBox()
        widget.get_value = lambda w=widget: bool(w.checkState())
        widget.set_value = lambda value, w=widget: bool(
            w.setCheckState(Qt.Checked if value else Qt.Unchecked)
        )

        api_widget = self.widgets_dic['anaconda_api_url']
        widget.set_warning = lambda w=widget: api_widget
        widget.stateChanged.connect(
            lambda v=None, w=widget: self.options_changed(widget=w)
        )
        self.create_widget(
            widget=widget,
            option=option,
            label=label,
            hint=hint,
            check=check,
            info=info,
        )

    def options_changed(self, value=None, widget=None):
        """Callback helper triggered on preference value change."""
        config_value = self.get_option(widget.option)

        if config_value != widget.get_value():
            self.widgets_changed.add(widget)
        else:
            if widget in self.widgets_changed:
                self.widgets_changed.remove(widget)

        self.button_ok.setDisabled(not bool(len(self.widgets_changed)))

    def widget_for_option(self, option):
        """Return the widget for the given option."""
        return self.widgets_dic[option]

    # --- API
    # -------------------------------------------------------------------------
    def set_initial_values(self):
        """
        Set configuration values found in other config files.

        Some options of configuration are found in condarc or in
        anaconda-client configuration.
        """
        self.config.set(
            'main', 'anaconda_api_url', self.api.client_get_api_url()
        )

        # See https://conda.io/docs/install/central.html
        # ssl_verify overloads True/False/<Path to certificate>
        # Navigator splits that into 2 separate options for clarity
        ssl_verify = self.api.client_get_ssl()
        if isinstance(ssl_verify, bool):
            self.config.set('main', 'ssl_verification', ssl_verify)
            self.config.set('main', 'ssl_certificate', None)
        else:
            self.config.set('main', 'ssl_verification', True)
            self.config.set('main', 'ssl_certificate', ssl_verify)

    def setup(self):
        """Setup the preferences dialog."""

        def api_url_checker(value):
            """
            Custom checker to use selected ssl option instead of stored one.

            This allows to set an unsafe api url directly on the preferences
            dialog. Without this, one would have to first disable, click
            accept, then open preferences again and change api url for it to
            work.
            """
            # Ssl widget
            ssl_widget = self.widgets_dic.get('ssl_verification')
            verify = ssl_widget.get_value() if ssl_widget else True

            # Certificate path
            ssl_cert_widget = self.widgets_dic.get('ssl_certificate')
            if ssl_cert_widget:
                verify = ssl_cert_widget.get_value()

            # Offline mode
            offline_widget = self.widgets_dic.get('offline_mode')
            if ssl_widget or ssl_cert_widget:
                offline_mode = offline_widget.get_value()
            else:
                offline_mode = False

            if offline_mode:
                basic_check = (
                    False,
                    'API Domain cannot be modified when '
                    'working in <b>offline mode</b>.<br>',
                )
            else:
                basic_check = self.is_valid_api(value, verify=verify)

            return basic_check

        def ssl_checker(value):
            """Counterpart to api_url_checker."""
            api_url_widget = self.widgets_dic.get('anaconda_api_url')
            api_url = api_url_widget.get_value()
            return self.is_valid_api(api_url, verify=value)

        def ssl_certificate_checker(value):
            """Check if certificate path is valid/exists."""
            ssl_widget = self.widgets_dic.get('ssl_verification')
            verify = ssl_widget.get_value() if ssl_widget else True
            ssl_cert_widget = self.widgets_dic.get('ssl_certificate')
            path = ssl_cert_widget.get_value()
            return self.is_valid_cert_file(path, verify)

        self.set_initial_values()
        self.create_textbox(
            'Anaconda API domain',
            'anaconda_api_url',
            check=api_url_checker,
        )
        self.create_checkbox(
            'Enable SSL verification',
            'ssl_verification',
            check=ssl_checker,
            hint=(
                '<i>Disabling this option is not <br>'
                'recommended for security reasons</i>'
            ),
        )
        self.create_textbox(
            'SSL certificate path (Optional)',
            'ssl_certificate',
            check=ssl_certificate_checker,
        )
        info = '''To help us improve Anaconda Navigator, fix bugs,
and make it even easier for everyone to use Python,
we gather anonymized usage information, just like
most web browsers and mobile apps.'''
        self.create_checkbox(
            'Quality improvement reporting',
            'provide_analytics',
            info=info,
        )
        info_offline = DialogOfflineMode.MESSAGE_PREFERENCES
        extra = '<br><br>' if WIN7 else ''
        self.create_checkbox(
            'Enable offline mode',
            'offline_mode',
            info=info_offline + extra,
        )
        self.create_checkbox('Hide offline mode dialog', 'hide_offline_dialog')
        self.create_checkbox('Hide quit dialog', 'hide_quit_dialog')
        self.create_checkbox(
            'Hide update dialog on startup', 'hide_update_dialog'
        )
        self.create_checkbox(
            'Hide running applications dialog', 'hide_running_apps_dialog'
        )
        self.create_checkbox(
            'Enable high DPI scaling', 'enable_high_dpi_scaling'
        )
        self.create_checkbox(
            'Show application startup error messages', 'show_application_launch_errors'
        )

        ssl_ver_widget = self.widgets_dic.get('ssl_verification')
        ssl_ver_widget.stateChanged.connect(self.enable_disable_cert)
        ssl_cert_widget = self.widgets_dic.get('ssl_certificate')
        ssl_cert_widget.setPlaceholderText(
            'Certificate to verify SSL connections'
        )

        # Refresh enabled/disabled status of certificate textbox
        self.enable_disable_cert()

    def enable_disable_cert(self, value=None):
        """Refresh enabled/disabled status of certificate textbox."""
        ssl_cert_widget = self.widgets_dic.get('ssl_certificate')
        if value:
            value = bool(value)
        else:
            ssl_ver_widget = self.widgets_dic.get('ssl_verification')
            value = bool(ssl_ver_widget.checkState())
        ssl_cert_widget.setEnabled(value)

    @staticmethod
    def update_icon(label, icon):
        """Update icon for information or warning."""
        pixmap = QPixmap(icon)
        label.setScaledContents(True)
        label.setPixmap(
            pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        )
        return label

    @staticmethod
    def warn(widget, text=None):
        """Display warning for widget in preferences."""
        label = widget.label_information
        if text:
            label = PreferencesDialog.update_icon(label, WARNING_ICON)
            label.setToolTip(str(text))
            w = widget.label_information.width() / 2
            h = widget.label_information.height() / 2
            position = widget.label_information.mapToGlobal(QPoint(w, h))
            QCursor.setPos(position)
        else:
            label.setPixmap(QPixmap())
            label.setToolTip('')

    # --- Checkers
    # -------------------------------------------------------------------------
    def is_valid_url(self, url):
        """Check if a given URL returns a 200 code."""
        output = self.api.download_is_valid_url(url, non_blocking=False)
        error = ''
        if not output:
            error = 'Invalid api url.'
        return output, error

    def is_valid_cert_file(self, path, verify):
        """"Check if ssl certificate file in given path exists."""
        output = True
        error = ''

        # Only validate if it is not empty and if ssl_verification is checked
        if path.strip() and verify:
            output = os.path.isfile(path)
            if not output:
                error = 'File not found.'
        return output, error

    def is_valid_api(self, url, verify=True):
        """Check if a given URL is a valid anaconda api endpoint."""
        output = self.api.download_is_valid_api_url(
            url,
            non_blocking=False,
            verify=verify,
        )
        error = ''
        if not output:
            url_api_1 = ''
            url_api_2 = ''

            if '/api' not in url and self.is_valid_url(url)[0]:
                url_api_1 = url.replace('https://', 'https://api.')
                url_api_1 = url_api_1.replace('http://', 'http://api.')

                if url.endswith('/'):
                    url_api_2 = url + 'api'
                else:
                    url_api_2 = url + '/api'

                error = (
                    'Invalid Anaconda API url. <br>'
                    '<br>Try using:<br><b>{0}</b> or <br>'
                    '<b>{1}</b>'.format(url_api_1, url_api_2)
                )
            else:
                error = (
                    'Invalid Anaconda API url.<br><br>'
                    'Check the url is valid and corresponds to the api '
                    'endpoint.'
                )
        return output, error

    def run_checks(self):
        """
        Run all check functions on configuration options.

        This method checks and warns but it does not change/set values.
        """
        checks = []
        for widget in self.widgets_changed:
            value = widget.get_value()
            check, error = widget.check_value(value)
            checks.append(check)

            if check:
                self.warn(widget)
            else:
                self.button_ok.setDisabled(True)
                widget.set_warning()
                self.warn(widget, error)
                break

        # Emit checks ready
        self.sig_check_ready.emit()
        return checks

    def reset_to_defaults(self):
        """Reset the preferences to the default values."""
        for widget in self.widgets:
            option = widget.option
            default = self.get_option_default(option)
            widget.set_value(default)

            # Flag all values as updated
            self.options_changed(widget=widget, value=default)
        self.sig_reset_ready.emit()

    def accept(self):
        """Override Qt method."""
        sig_updated = False
        anaconda_api_url = None
        checks = self.run_checks()

        # Update values
        if checks and all(checks):
            for widget in self.widgets_changed:
                value = widget.get_value()
                self.set_option(widget.option, value)

                # Settings not stored on Navigator config, but taken from
                # anaconda-client config
                if widget.option == 'anaconda_api_url':
                    anaconda_api_url = value  # Store it to be emitted
                    self.api.client_set_api_url(value)
                    sig_updated = True

                # ssl_verify/verify_ssl handles True/False/<Path to cert>
                # On navi it is split in 2 options for clarity
                if widget.option in ['ssl_certificate', 'ssl_verification']:
                    ssl_veri = self.widgets_dic.get('ssl_verification')
                    ssl_cert = self.widgets_dic.get('ssl_certificate')
                    verify = ssl_veri.get_value()
                    path = ssl_cert.get_value()

                    if path.strip() and verify:
                        value = path
                    else:
                        value = verify

                    self.api.client_set_ssl(value)

            if sig_updated and anaconda_api_url:

                def _api_info(worker, output, error):
                    conda_url = output.get('conda_url')
                    try:
                        self.sig_urls_updated.emit(anaconda_api_url, conda_url)
                        super(PreferencesDialog, self).accept()
                    except RuntimeError:
                        # Some tests on appveyor/circleci fail
                        pass

                worker = self.api.api_urls()
                worker.sig_chain_finished.connect(_api_info)

            super(PreferencesDialog, self).accept()
Esempio n. 21
0
    def __init__(self, parent=None):
        """Dialog to add delete and select active conda pacakge channels ."""
        super(DialogChannels, self).__init__(parent)
        self._parent = parent
        self._conda_url = 'https://conda.anaconda.org'
        self.api = AnacondaAPI()
        self.initial_sources = None
        self.config_sources = None
        self.style_sheet = None
        self._setup_ready = False
        self._conda_url_setup_ready = False

        # Widgets
        self.list = ListWidgetChannels(parent=self, api=self.api)
        self.label_info = LabelBase(
            'Manage channels you want Navigator to include.')
        self.label_status = LabelBase('Collecting sources...')
        self.progress_bar = QProgressBar(self)
        self.button_add = ButtonNormal('Add...')
        self.button_cancel = ButtonNormal('Cancel')
        self.button_ok = ButtonPrimary('Update channels')

        # Widget setup
        self.frame_title_bar.setVisible(False)
        self.list.setFrameStyle(QFrame.NoFrame)
        self.list.setFrameShape(QFrame.NoFrame)
        self.setWindowFlags(self.windowFlags() | Qt.Popup)
        self.setWindowOpacity(0.96)
        self.setMinimumHeight(300)
        self.setMinimumWidth(self.WIDTH)
        self.setModal(True)

        # Layout
        layout_button = QHBoxLayout()
        layout_button.addWidget(self.label_info)
        layout_button.addStretch()
        layout_button.addWidget(self.button_add)

        layout_ok = QHBoxLayout()
        layout_ok.addWidget(self.label_status)
        layout_ok.addWidget(SpacerHorizontal())
        layout_ok.addWidget(self.progress_bar)
        layout_ok.addWidget(SpacerHorizontal())
        layout_ok.addStretch()
        layout_ok.addWidget(self.button_cancel)
        layout_ok.addWidget(SpacerHorizontal())
        layout_ok.addWidget(self.button_ok)

        layout = QVBoxLayout()
        layout.addLayout(layout_button)
        layout.addWidget(SpacerVertical())
        layout.addWidget(self.list)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_ok)
        self.setLayout(layout)

        # Signals
        self.button_add.clicked.connect(self.add_channel)
        self.button_ok.clicked.connect(self.update_channels)
        self.button_cancel.clicked.connect(self.reject)
        self.list.sig_status_updated.connect(self.update_status)
        self.list.sig_channel_added.connect(
            lambda v=None: self.set_tab_order())
        self.list.sig_channel_added.connect(
            lambda v=None: self.button_ok.setFocus())
        self.list.sig_channel_removed.connect(
            lambda v=None: self.set_tab_order())
        self.list.sig_channel_removed.connect(
            lambda v=None: self.button_ok.setFocus())
        self.list.sig_channel_checked.connect(self.sig_check_ready)
        self.list.sig_channel_status.connect(self.refresh)

        self.button_add.setDisabled(True)
        self.button_ok.setDisabled(True)
        self.button_cancel.setDisabled(True)
        self.update_status(action='Collecting sources...',
                           value=0,
                           max_value=0)
Esempio n. 22
0
class ProjectsWidget(WidgetBase):
    """Main projects widget."""

    sig_saved = Signal()
    sig_login_requested = Signal()

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

        self.api = AnacondaAPI()
        self.timer = None
        self.timer_content_changed = QTimer()
        self.project_path = None
        self.original_content = None
        self.config = CONF
        self.timer = None

        # Widgets
        self.frame_projects_header = FrameProjectDetailsHeader()
        self.frame_projects_footer = FrameProjectDetailsFooter()
        self.button_upload = ButtonPrimary('Upload to Anaconda Cloud')
        self.button_cancel = ButtonDanger('Cancel')
        self.label_project_location = LabelProjectLocation(
            '<b>Project location</b>')
        self.label_status_message = LabelBase('')
        self.text_project_location = TextProjectLocation()
        self.tab_details = QTabWidget()
        self.file_explorer = ExplorerWidget()
        self.editor = ProjectEditor(parent=self)

        # Wigets setup
        tabbar = self.tab_details.tabBar()
        tabbar.setFocusPolicy(Qt.StrongFocus)
        self.tab_details.addTab(self.file_explorer, 'Files')
        self.tab_details.addTab(self.editor, 'Edit')
        self.timer_content_changed.setInterval(2000)
        self.timer_content_changed.timeout.connect(self.check_content_change)
        self.timer_content_changed.start()

        # Layouts

        layout_upload = QHBoxLayout()
        layout_upload.addWidget(SpacerHorizontal())
        layout_upload.addWidget(SpacerHorizontal())
        layout_upload.addWidget(self.label_status_message)
        layout_upload.addStretch()
        layout_upload.addWidget(self.button_cancel)
        layout_upload.addWidget(SpacerHorizontal())
        layout_upload.addWidget(self.button_upload)
        layout_upload.addWidget(SpacerHorizontal())
        layout_upload.addWidget(SpacerHorizontal())

        layout_footer = QVBoxLayout()
        layout_footer.addWidget(SpacerVertical())
        layout_footer.addWidget(self.tab_details)
        layout_footer.addLayout(layout_upload)
        layout_footer.addWidget(SpacerVertical())
        layout_footer.addWidget(SpacerVertical())
        self.frame_projects_footer.setLayout(layout_footer)

        layout = QVBoxLayout()
        layout.addWidget(self.frame_projects_footer)
        self.setLayout(layout)

        # Signals
        self.editor.sig_dirty_state.connect(self.set_dirty)
        self.editor.sig_saved.connect(self.save)
        self.button_upload.clicked.connect(self.upload)
        self.button_cancel.clicked.connect(self.cancel)
        self.file_explorer.sig_add_to_project.connect(self.add_to_project)
        self.button_cancel.setVisible(False)

        self.file_explorer.set_current_folder(HOME_PATH)

    def add_to_project(self, fname):
        """Add selected file to project."""
        file_path = os.path.join(
            self.project_path,
            os.path.basename(fname),
        )
        try:
            shutil.copyfile(fname, file_path)
        except Exception:
            pass

    def check_content_change(self):
        """Check if content of anaconda-project changed outside."""
        if self.project_path:
            project_config_path = os.path.join(self.project_path,
                                               'anaconda-project.yml')
            if os.path.isfile(project_config_path):
                current_content = self.editor.text()
                with open(project_config_path, 'r') as f:
                    data = f.read()

                if (current_content != data and data != self.original_content):
                    self.load_project(self.project_path)

    def set_dirty(self, state):
        """Set dirty state editor tab."""
        text = 'Edit*' if state else 'Edit'
        self.tab_details.setTabText(1, text)

    def before_delete(self):
        """Before deleting a folder, ensure it is not the same as the cwd."""
        self.file_explorer.set_current_folder(HOME_PATH)

    def clear(self):
        """Reset view for proect details."""
        self.text_project_location.setText('')
        self.editor.set_text('')

    def cancel(self):
        """Cancel ongoing project process."""
        # TODO: when running project. Cancel ongoing process!
        self.button_cancel.setVisible(False)
        self.button_upload.setEnabled(True)

    def _upload(self, worker, output, error):
        """Upload callback."""
        error = error if error else ''
        errors = []
        if output is not None:
            errors = output.errors
        # print(output.status_description)
        # print(output.logs)
        # print(errors)
        if error or errors:
            if errors:
                error_msg = error or '\n'.join(errors)
            elif error:
                error_msg = 'Upload failed!'
            self.update_status(error_msg)
        else:
            self.update_status('Project <b>{0}</b> upload successful'.format(
                worker.name))

        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.setInterval(10000)
        self.timer.timeout.connect(lambda: self.update_status(''))
        self.timer.start()
        self.button_upload.setEnabled(True)
        self.button_cancel.setVisible(False)

    def update_status(self, message):
        """Update Status Bar message."""
        self.label_status_message.setText(message)

    def upload(self):
        """Upload project to Anaconda Cloud."""
        # Check if is logged in?
        if not self.api.is_logged_in():
            self.update_status('You need to log in to Anaconda Cloud')
            self.sig_login_requested.emit()
            self.update_status('')
            return

        project = self.api.project_load(self.project_path)
        name = project.name or os.path.basename(self.project_path)

        # Check if saved?
        if self.editor.is_dirty():
            self.update_status('Saving project <b>{0}</b>'.format(
                project.name))
            self.editor.save()

        project = self.api.project_load(self.project_path)

        if not project.problems:
            username, token = self.api.get_username_token()
            self.button_cancel.setVisible(True)
            worker = self.api.project_upload(
                project,
                username=username,
                token=token,
            )
            worker.sig_finished.connect(self._upload)
            worker.name = project.name
            self.button_upload.setEnabled(False)
            msg = 'Uploading project <b>{0}</b> to Anaconda Cloud.'.format(
                project.name)
            self.update_status(msg)
        else:
            self.update_status(
                'Problems must be fixed before uploading <b>{0}</b>'
                ''.format(name))

    def save(self):
        """Save current edited project."""
        project_config_path = os.path.join(self.project_path,
                                           'anaconda-project.yml')
        data = self.editor.text()
        if os.path.isfile(project_config_path):
            with open(project_config_path, 'w') as f:
                data = f.write(data)
        self.load_project(self.project_path, overwrite=False)
        self.sig_saved.emit()

    def load_project(self, project_path, overwrite=True):
        """Load a conda project located at path."""
        self.project_path = project_path
        project = self.api.project_load(project_path)
        self.project = project
        self.text_project_location.setText(project_path)
        self.file_explorer.set_current_folder(project_path)

        project_config_path = os.path.join(project_path,
                                           'anaconda-project.yml')
        data = ''
        if os.path.isfile(project_config_path):
            with open(project_config_path, 'r') as f:
                data = f.read()

        self.original_content = data
        if overwrite:
            self.editor.set_text(data)

        self.set_dirty(False)
        self.file_explorer.set_home(project_path)
        self.update_error_status(project)
        self.update_status('')

    def ordered_widgets(self):
        """Return a list of the ordered widgets."""
        tabbar = self.tab_details.tabBar()
        ordered_widgets = [tabbar]
        ordered_widgets += self.file_explorer.ordered_widgets()
        ordered_widgets += self.editor.ordered_widgets()
        ordered_widgets += [self.button_upload]
        return ordered_widgets

    def update_error_status(self, project):
        """Update problems and suggestions."""
        if project:
            problems = project.problems
            suggestions = project.suggestions
            if problems or suggestions:
                icon = QIcon(WARNING_ICON)
                self.tab_details.setTabIcon(1, icon)
            else:
                self.tab_details.setTabIcon(1, QIcon())
            self.editor.set_info(problems, suggestions)
Esempio n. 23
0
    def __init__(self, *args, **kwargs):
        super(ProjectsWidget, self).__init__(*args, **kwargs)

        self.api = AnacondaAPI()
        self.timer = None
        self.timer_content_changed = QTimer()
        self.project_path = None
        self.original_content = None
        self.config = CONF
        self.timer = None

        # Widgets
        self.frame_projects_header = FrameProjectDetailsHeader()
        self.frame_projects_footer = FrameProjectDetailsFooter()
        self.button_upload = ButtonPrimary('Upload to Anaconda Cloud')
        self.button_cancel = ButtonDanger('Cancel')
        self.label_project_location = LabelProjectLocation(
            '<b>Project location</b>')
        self.label_status_message = LabelBase('')
        self.text_project_location = TextProjectLocation()
        self.tab_details = QTabWidget()
        self.file_explorer = ExplorerWidget()
        self.editor = ProjectEditor(parent=self)

        # Wigets setup
        tabbar = self.tab_details.tabBar()
        tabbar.setFocusPolicy(Qt.StrongFocus)
        self.tab_details.addTab(self.file_explorer, 'Files')
        self.tab_details.addTab(self.editor, 'Edit')
        self.timer_content_changed.setInterval(2000)
        self.timer_content_changed.timeout.connect(self.check_content_change)
        self.timer_content_changed.start()

        # Layouts

        layout_upload = QHBoxLayout()
        layout_upload.addWidget(SpacerHorizontal())
        layout_upload.addWidget(SpacerHorizontal())
        layout_upload.addWidget(self.label_status_message)
        layout_upload.addStretch()
        layout_upload.addWidget(self.button_cancel)
        layout_upload.addWidget(SpacerHorizontal())
        layout_upload.addWidget(self.button_upload)
        layout_upload.addWidget(SpacerHorizontal())
        layout_upload.addWidget(SpacerHorizontal())

        layout_footer = QVBoxLayout()
        layout_footer.addWidget(SpacerVertical())
        layout_footer.addWidget(self.tab_details)
        layout_footer.addLayout(layout_upload)
        layout_footer.addWidget(SpacerVertical())
        layout_footer.addWidget(SpacerVertical())
        self.frame_projects_footer.setLayout(layout_footer)

        layout = QVBoxLayout()
        layout.addWidget(self.frame_projects_footer)
        self.setLayout(layout)

        # Signals
        self.editor.sig_dirty_state.connect(self.set_dirty)
        self.editor.sig_saved.connect(self.save)
        self.button_upload.clicked.connect(self.upload)
        self.button_cancel.clicked.connect(self.cancel)
        self.file_explorer.sig_add_to_project.connect(self.add_to_project)
        self.button_cancel.setVisible(False)

        self.file_explorer.set_current_folder(HOME_PATH)
Esempio n. 24
0
    def __init__(self, parent=None):
        """License Manager main dialog."""
        super(LicenseManagerDialog, self).__init__(parent=parent)

        self.api = AnacondaAPI()

        # Widgets
        self.message_box = None  # For testing
        self.button_add = ButtonPrimary('Add license')
        self.button_ok = ButtonNormal('Close')
        self.button_remove = ButtonNormal('Remove license')
        self.button_contact = ButtonLink('Please contact us.')
        self.label_info = LabelBase('Manage your Continuum Analytics '
                                    'license keys.')
        self.label_contact = LabelBase('Got a problem with your license? ')
        self.proxy_model = QSortFilterProxyModel(parent=self)
        self.model = LicenseModel(parent=self)
        self.table = LicenseTableView(parent=self)
        self.delegate = BackgroundDelegate(self.table)

        # Widget setup
        self.proxy_model.setSourceModel(self.model)
        self.table.setItemDelegate(self.delegate)
        self.table.setModel(self.proxy_model)
        self.setWindowTitle('License Manager')

        # Layouts
        layout_buttons = QHBoxLayout()
        layout_buttons.addWidget(self.label_info)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addStretch()
        layout_buttons.addWidget(self.button_add)
        layout_buttons.addWidget(SpacerHorizontal())
        layout_buttons.addWidget(self.button_remove)

        layout_buttons_bottom = QHBoxLayout()
        layout_buttons_bottom.addWidget(self.label_contact)
        layout_buttons_bottom.addWidget(self.button_contact)
        layout_buttons_bottom.addStretch()
        layout_buttons_bottom.addWidget(self.button_ok)

        layout = QVBoxLayout()
        layout.addLayout(layout_buttons)
        layout.addWidget(SpacerVertical())
        layout.addWidget(self.table)
        layout.addWidget(SpacerVertical())
        layout.addWidget(SpacerVertical())
        layout.addLayout(layout_buttons_bottom)
        self.setLayout(layout)

        # Signals
        self.button_add.clicked.connect(lambda: self.add_license())
        self.button_remove.clicked.connect(self.remove_license)
        self.button_ok.clicked.connect(self.accept)
        self.button_contact.clicked.connect(
            lambda v=None: self.sig_url_clicked.emit(self.CONTACT_LINK,
                                                     'License Manager'))
        self.table.sig_dropped.connect(self.handle_drop)

        # Setup
        self.button_add.setFocus()
        self.load_licenses()
Esempio n. 25
0
File: apps.py Progetto: bopopescu/QC
    def __init__(
        self,
        name=None,
        display_name=None,
        description=None,
        command=None,
        versions=None,
        image_path=None,
        prefix=None,
        needs_license=False,
        non_conda=False,
    ):
        """Item with custom widget for the applications list."""
        super(ListItemApplication, self).__init__()

        self.api = AnacondaAPI()
        self.prefix = prefix
        self.name = name
        self.display_name = display_name if display_name else name
        self.url = ''
        self.expired = False
        self.needs_license = needs_license
        self.description = description
        self.command = command
        self.versions = versions
        self.image_path = image_path if image_path else ANACONDA_ICON_256_PATH
        self.style_sheet = None
        self.timeout = 2000
        self.non_conda = non_conda
        self._vscode_version_value = None

        # Widgets
        self.button_install = ButtonApplicationInstall("Install")  # or Try!
        self.button_launch = ButtonApplicationLaunch("Launch")
        self.button_options = ButtonApplicationOptions()
        self.label_license = LabelApplicationLicense('')
        self.button_license = ButtonApplicationLicense('')
        self.label_icon = LabelApplicationIcon()
        self.label_name = LabelApplicationName(self.display_name)
        self.label_description = LabelApplicationDescription(self.description)
        self.button_version = ButtonApplicationVersion(
            to_text_string(self.version))
        self.menu_options = QMenu('Application options')
        self.menu_versions = QMenu('Install specific version')
        self.pixmap = QPixmap(self.image_path)
        self.timer = QTimer()
        self.widget = WidgetApplication()
        self.frame_spinner = FrameApplicationSpinner()
        self.spinner = NavigatorSpinner(self.widget, total_width=16)
        lay = QHBoxLayout()
        lay.addWidget(self.spinner)
        self.frame_spinner.setLayout(lay)

        # Widget setup
        self.button_version.setFocusPolicy(Qt.NoFocus)
        self.button_version.setEnabled(True)
        self.label_description.setAlignment(Qt.AlignCenter)
        self.timer.setInterval(self.timeout)
        self.timer.setSingleShot(True)
        self.label_icon.setPixmap(self.pixmap)
        self.label_icon.setScaledContents(True)  # important on High DPI!
        self.label_icon.setMaximumWidth(self.ICON_SIZE)
        self.label_icon.setMaximumHeight(self.ICON_SIZE)
        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.frame_spinner.setVisible(False)

        # Layouts
        layout_spinner = QHBoxLayout()
        layout_spinner.addWidget(self.button_version, 0, Qt.AlignCenter)
        layout_spinner.addWidget(self.frame_spinner, 0, Qt.AlignCenter)

        layout_license = QHBoxLayout()
        layout_license.addStretch()
        layout_license.addWidget(self.label_license, 0, Qt.AlignCenter)
        layout_license.addWidget(self.button_license, 0, Qt.AlignCenter)
        layout_license.addStretch()

        layout_main = QVBoxLayout()
        layout_main.addWidget(self.button_options, 0, Qt.AlignRight)
        layout_main.addWidget(self.label_icon, 0, Qt.AlignCenter)
        layout_main.addWidget(self.label_name, 0, Qt.AlignCenter)
        layout_main.addLayout(layout_spinner)
        layout_main.addLayout(layout_license)
        layout_main.addWidget(self.label_description, 0, Qt.AlignCenter)
        layout_main.addWidget(self.button_launch, 0, Qt.AlignCenter)
        layout_main.addWidget(self.button_install, 0, Qt.AlignCenter)

        self.widget.setLayout(layout_main)
        self.widget.setStyleSheet(load_style_sheet())
        self.setSizeHint(self.widget_size())
        # This might help with visual quirks on the home screen
        self.widget.setMinimumSize(self.widget_size())

        # 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.button_license.clicked.connect(self.launch_url)
        self.timer.timeout.connect(self._application_launched)

        # Setup
        self.update_status()
Esempio n. 26
0
class _GATracker(object):
    """Google analytics tracker."""

    TRACKER_ID = 'UA-27761864-8'

    def __init__(self):
        """Google analytics tracker."""
        # Avoid blocking the UI
        self._queue = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()
        self.api = AnacondaAPI()

        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

        self.setup()

    def setup(self):
        """Setup the tracker initial configuration."""
        # Set tracker config
        try:
            user = self.api.client_user().get('login', '@anonymous')
        except Exception:
            user = '******'

        domain = self.api.client_domain()
        normalized_user = domain + '/' + user
        self.tracker = Tracker.create(
            self.TRACKER_ID,
            client_id=normalized_user,
            user_id=normalized_user,
        )

        pkg_version = self.api.conda_package_version
        # Set custom variables
        python_version = '.'.join([str(i) for i in sys.version_info[:3]])
        operating_system = ";".join([platform.uname()[0]] +
                                    list(platform.uname()[2:]), )
        pyqt_version = (pkg_version(pkg='pyqt5', name='root')
                        or pkg_version(pkg='pyqt4', name='root')
                        or pkg_version(pkg='pyqt', name='root'))
        conda_version = pkg_version(pkg='conda', name='root')

        # Track initial global data events
        self.track_event('application', 'python-version', label=python_version)
        self.track_event('application', 'language', label=self.get_language())
        self.track_event('application',
                         'screen-resolution',
                         label=self.get_screen_resolution())
        self.track_event('application',
                         'operating-system',
                         label=operating_system)
        self.track_event('application', 'version', label=app_version)
        self.track_event('application',
                         'platform',
                         label=self.api.conda_platform())
        self.info = {
            'python': python_version,
            'language': self.get_language(),
            'os': operating_system,
            'version': app_version,
            'platform': self.api.conda_platform(),
            'qt': QT_VERSION,
            'pyqt': pyqt_version,
            'conda': conda_version
        }

    def _clean(self):
        """Check for inactive workers and remove their references."""
        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
        else:
            self._timer.stop()

    def _start(self):
        """Start the next item in the worker queue."""
        if len(self._queue) == 1:
            thread = self._queue.popleft()
            thread.start()
            self._timer.start()

    def _create_worker(self, method, *args, **kwargs):
        """Create a worker for this client to be run in a separate thread."""
        provide_analytics = CONF.get('main', 'provide_analytics')

        thread = QThread()
        worker = AnalyticsWorker(method, args, kwargs)

        if provide_analytics:
            worker.moveToThread(thread)
            worker.sig_finished.connect(self._start)
            worker.sig_finished.connect(thread.quit)
            thread.started.connect(worker.start)
            self._queue.append(thread)
            self._threads.append(thread)
            self._workers.append(worker)
            self._start()

        return worker

    def track_event(self, category, action, label=None):
        """Track analytyics event."""
        return self._create_worker(
            self.tracker.send,
            'event',
            category=category,
            action=action,
            label=str(label),
        )

    def track_page(self, page, pagetitle=None):
        """
        Track analytyics page.

        Page in the context of Navigator is a tab, a dialog, a message box.
        """
        if pagetitle is None:
            title = ' '.join([i for i in page.split('/') if i])
        else:
            title = pagetitle

        title = title.capitalize()
        return self._create_worker(
            self.tracker.send,
            'pageview',
            page=page,
            pagetitle=title,
        )

    def set_client_id(self, client_id=None):
        """Set the client tracker ID."""
        self.tracker = Tracker.create('UA-74661388-1',
                                      client_id=client_id,
                                      user_id=client_id)

    def get_ip(self):
        """Return the current ip based on ipify.org."""
        try:
            response = requests.get('https://api.ipify.org/?format=json',
                                    proxies=self.api.conda_load_proxy_config())
            ip = response.json()['ip']
        except Exception as error:
            logger.error(str(error))
            ip = None

        return ip

    @staticmethod
    def get_language():
        """Return the locale language."""
        # Process locale language
        try:
            lang = locale.getdefaultlocale()[0]
        except Exception:
            lang = None

        if not lang:
            lang = 'en'

        return lang

    @staticmethod
    def get_screen_resolution():
        """Return the screen resolution of the primary screen."""
        try:
            widget = QDesktopWidget()
            geometry = widget.availableGeometry(widget.primaryScreen())
            value = "{0}x{1}".format(geometry.width(), geometry.height())
        except Exception:
            value = None
        return value
Esempio n. 27
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)
Esempio n. 28
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)
Esempio n. 29
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)
Esempio n. 30
0
File: apps.py Progetto: bopopescu/QC
class ListItemApplication(ListWidgetItemBase):
    """Item with custom widget for the applications list."""

    ICON_SIZE = 64

    def __init__(
        self,
        name=None,
        display_name=None,
        description=None,
        command=None,
        versions=None,
        image_path=None,
        prefix=None,
        needs_license=False,
        non_conda=False,
    ):
        """Item with custom widget for the applications list."""
        super(ListItemApplication, self).__init__()

        self.api = AnacondaAPI()
        self.prefix = prefix
        self.name = name
        self.display_name = display_name if display_name else name
        self.url = ''
        self.expired = False
        self.needs_license = needs_license
        self.description = description
        self.command = command
        self.versions = versions
        self.image_path = image_path if image_path else ANACONDA_ICON_256_PATH
        self.style_sheet = None
        self.timeout = 2000
        self.non_conda = non_conda
        self._vscode_version_value = None

        # Widgets
        self.button_install = ButtonApplicationInstall("Install")  # or Try!
        self.button_launch = ButtonApplicationLaunch("Launch")
        self.button_options = ButtonApplicationOptions()
        self.label_license = LabelApplicationLicense('')
        self.button_license = ButtonApplicationLicense('')
        self.label_icon = LabelApplicationIcon()
        self.label_name = LabelApplicationName(self.display_name)
        self.label_description = LabelApplicationDescription(self.description)
        self.button_version = ButtonApplicationVersion(
            to_text_string(self.version))
        self.menu_options = QMenu('Application options')
        self.menu_versions = QMenu('Install specific version')
        self.pixmap = QPixmap(self.image_path)
        self.timer = QTimer()
        self.widget = WidgetApplication()
        self.frame_spinner = FrameApplicationSpinner()
        self.spinner = NavigatorSpinner(self.widget, total_width=16)
        lay = QHBoxLayout()
        lay.addWidget(self.spinner)
        self.frame_spinner.setLayout(lay)

        # Widget setup
        self.button_version.setFocusPolicy(Qt.NoFocus)
        self.button_version.setEnabled(True)
        self.label_description.setAlignment(Qt.AlignCenter)
        self.timer.setInterval(self.timeout)
        self.timer.setSingleShot(True)
        self.label_icon.setPixmap(self.pixmap)
        self.label_icon.setScaledContents(True)  # important on High DPI!
        self.label_icon.setMaximumWidth(self.ICON_SIZE)
        self.label_icon.setMaximumHeight(self.ICON_SIZE)
        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.frame_spinner.setVisible(False)

        # Layouts
        layout_spinner = QHBoxLayout()
        layout_spinner.addWidget(self.button_version, 0, Qt.AlignCenter)
        layout_spinner.addWidget(self.frame_spinner, 0, Qt.AlignCenter)

        layout_license = QHBoxLayout()
        layout_license.addStretch()
        layout_license.addWidget(self.label_license, 0, Qt.AlignCenter)
        layout_license.addWidget(self.button_license, 0, Qt.AlignCenter)
        layout_license.addStretch()

        layout_main = QVBoxLayout()
        layout_main.addWidget(self.button_options, 0, Qt.AlignRight)
        layout_main.addWidget(self.label_icon, 0, Qt.AlignCenter)
        layout_main.addWidget(self.label_name, 0, Qt.AlignCenter)
        layout_main.addLayout(layout_spinner)
        layout_main.addLayout(layout_license)
        layout_main.addWidget(self.label_description, 0, Qt.AlignCenter)
        layout_main.addWidget(self.button_launch, 0, Qt.AlignCenter)
        layout_main.addWidget(self.button_install, 0, Qt.AlignCenter)

        self.widget.setLayout(layout_main)
        self.widget.setStyleSheet(load_style_sheet())
        self.setSizeHint(self.widget_size())
        # This might help with visual quirks on the home screen
        self.widget.setMinimumSize(self.widget_size())

        # 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.button_license.clicked.connect(self.launch_url)
        self.timer.timeout.connect(self._application_launched)

        # Setup
        self.update_status()

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

    # --- Helpers
    # -------------------------------------------------------------------------
    def update_style_sheet(self, style_sheet=None):
        """Update custom CSS stylesheet."""
        if style_sheet:
            self.style_sheet = style_sheet
        else:
            self.style_sheet = load_style_sheet()

        self.menu_options.setStyleSheet(self.style_sheet)
        self.menu_versions.setStyleSheet(self.style_sheet)

    def ordered_widgets(self):
        """Return a list of the ordered widgets."""
        return [
            self.button_license, self.button_install, self.button_launch,
            self.button_options
        ]

    @staticmethod
    def widget_size():
        """Return the size defined in the SASS file."""
        return QSize(SASS_VARIABLES.WIDGET_APPLICATION_TOTAL_WIDTH,
                     SASS_VARIABLES.WIDGET_APPLICATION_TOTAL_HEIGHT)

    def launch_url(self):
        """Launch signal for url click."""
        self.widget.sig_url_clicked.emit(self.url)

    def actions_menu_requested(self):
        """Create and display 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 and self.installed:
                action.setChecked(True)
                action.setDisabled(True)

            version_actions.append(action)

        install_action = create_action(
            self.widget,
            'Install application',
            triggered=lambda: self.install_application())
        install_action.setEnabled(not self.installed)

        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)

        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            update_action.setDisabled(True)

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

        actions = [
            install_action, 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(len(versions) > 1)
        self.menu_options.move(position + offset)
        self.menu_options.exec_()

    def update_status(self):
        """Update status."""
        # License check
        license_label_text = ''
        license_url_text = ''
        self.url = ''
        self.expired = False
        button_label = 'Install'

        if self.needs_license:
            # TODO: Fix this method to use the api
            license_info = self.api.get_package_license(self.name)
            license_days = self.api.get_days_left(license_info)
            end_date = license_info.get('end_date', '')
            self.expired = license_days == 0
            plural = 's' if license_days != 1 else ''
            is_trial = license_info.get('type', '').lower() == 'trial'

            if self.installed and license_info:
                if is_trial and not self.expired:
                    license_label_text = ('Trial, {days} day{plural} '
                                          'remaining'.format(days=license_days,
                                                             plural=plural))
                    self.url = ''
                elif is_trial and self.expired:
                    license_label_text = 'Trial expired, '
                    license_url_text = 'contact us'
                    self.url = 'mailto:[email protected]'
                elif not is_trial and not self.expired:
                    license_label_text = 'License expires {}'.format(end_date)
                    self.url = ''
                elif not is_trial and self.expired:
                    license_url_text = 'Renew license'
                    self.url = 'mailto:[email protected]'
            elif self.installed and not bool(license_info):
                # Installed but no license found!
                license_url_text = 'No license found'
                self.url = 'mailto:[email protected]'
            else:
                if not self.expired:
                    button_label = 'Install'
                else:
                    button_label = 'Try'

        self.button_license.setText(license_url_text)
        self.button_license.setVisible(bool(self.url))
        self.label_license.setText(license_label_text)
        self.label_license.setVisible(bool(license_label_text))

        # Version and version updates
        if (self.versions and self.version != self.versions[-1]
                and self.installed):
            # The property is used with CSS to display updatable packages.
            self.button_version.setProperty('pressed', True)
            self.button_version.setToolTip('Version {0} available'.format(
                self.versions[-1]))
        else:
            self.button_version.setProperty('pressed', False)

        # For VScode app do not display if new updates are available
        # See: https://github.com/ContinuumIO/navigator/issues/1504
        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            self.button_version.setProperty('pressed', False)
            self.button_version.setToolTip('')

        if not self.needs_license:
            self.button_install.setText(button_label)
            self.button_install.setVisible(not self.installed)
            self.button_launch.setVisible(self.installed)
        else:
            self.button_install.setText('Try' if self.expired else 'Install')
            self.button_launch.setVisible(not self.expired)
            self.button_install.setVisible(self.expired)

        self.button_launch.setEnabled(True)

    def update_versions(self, version=None, versions=None):
        """Update button visibility depending on update availability."""
        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()

    def set_loading(self, value):
        """Set loading status."""
        self.button_install.setDisabled(value)
        self.button_options.setDisabled(value)
        self.button_launch.setDisabled(value)
        self.button_license.setDisabled(value)

        if value:
            self.spinner.start()
        else:
            self.spinner.stop()
            if self.version is None and self.versions is not None:
                version = self.versions[-1]
            else:
                version = self.version
            self.button_version.setText(version)
            self.button_launch.setDisabled(self.expired)

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

    # --- Helpers using api
    # -------------------------------------------------------------------------
    def _vscode_version(self):
        """Query the vscode version for the default installation path."""
        version = None
        if self._vscode_version_value is None:
            cmd = [self.api.vscode_executable(), '--version']
            # print(cmd)
            import subprocess
            try:
                output = subprocess.check_output(cmd)
                if PY3:
                    output = output.decode()
                output = [o for o in output.split('\n') if o and '.' in o]
                # print(output)
                if output:
                    version = output[0]
            except Exception:
                pass
                # print(e)

            self._vscode_version_value = version
        else:
            version = self._vscode_version_value

        return version

    @property
    def installed(self):
        """Return the installed status of the package."""
        version = None
        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            # TODO: Vscode program location, check existence
            version = self._vscode_version()
        elif self.prefix:
            version = self.api.conda_package_version(prefix=self.prefix,
                                                     pkg=self.name,
                                                     build=False)
        return bool(version)

    @property
    def version(self):
        """Return the current installed version or the highest version."""
        version = None
        if self.non_conda and self.name == GLOBAL_VSCODE_APP:
            version = self._vscode_version()
        elif self.prefix:
            version = self.api.conda_package_version(prefix=self.prefix,
                                                     pkg=self.name,
                                                     build=False)

        if not version:
            version = self.versions[-1]

        return version

    # --- Application actions
    # ------------------------------------------------------------------------
    def install_application(self, value=None, version=None, install=True):
        """
        Update the application on the defined prefix environment.

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

        action = C.APPLICATION_INSTALL if install else C.APPLICATION_UPDATE
        self.widget.sig_conda_action_requested.emit(
            action,
            self.name,
            version,
            C.TAB_HOME,
            self.non_conda,
        )
        self.set_loading(True)

    def remove_application(self):
        """Remove the application from the defined prefix environment."""
        self.widget.sig_conda_action_requested.emit(
            C.APPLICATION_REMOVE,
            self.name,
            None,
            C.TAB_HOME,
            self.non_conda,
        )
        self.set_loading(True)

    def update_application(self):
        """Update the application on the defined prefix environment."""
        self.install_application(version=self.versions[-1], install=False)

    def launch_application(self):
        """Launch application installed in prefix environment."""
        leave_path_alone = False
        if self.command is not None:
            if self.non_conda and self.name == GLOBAL_VSCODE_APP:
                leave_path_alone = True
                args = [self.command]
            else:
                args = self.command.split(' ')
                leave_path_alone = True

            self.button_launch.setDisabled(True)
            self.timer.setInterval(self.timeout)
            self.timer.start()
            update_pointer(Qt.BusyCursor)
            self.widget.sig_launch_action_requested.emit(
                self.name,
                args,
                leave_path_alone,
                self.prefix,
                C.TAB_HOME,
                self.non_conda,
            )