| /******************************************************************************* |
| * Copyright (c) 2011, 2020 GitHub Inc. and others |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Kevin Sawicki (GitHub Inc.) - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.mylyn.internal.github.ui; |
| |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.egit.github.core.Repository; |
| import org.eclipse.egit.github.core.User; |
| import org.eclipse.egit.github.core.client.GitHubClient; |
| import org.eclipse.egit.github.core.service.OrganizationService; |
| import org.eclipse.egit.github.core.service.RepositoryService; |
| import org.eclipse.egit.ui.internal.components.FilteredCheckboxTree; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.viewers.CheckStateChangedEvent; |
| import org.eclipse.jface.viewers.CheckboxTreeViewer; |
| import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; |
| import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; |
| import org.eclipse.jface.viewers.ICheckStateListener; |
| import org.eclipse.jface.viewers.ILabelProvider; |
| import org.eclipse.jface.viewers.ILabelProviderListener; |
| import org.eclipse.jface.viewers.StyledString; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.jface.viewers.ViewerComparator; |
| import org.eclipse.jface.wizard.WizardPage; |
| import org.eclipse.mylyn.internal.github.core.GitHub; |
| import org.eclipse.mylyn.internal.github.core.GitHubException; |
| import org.eclipse.mylyn.internal.github.core.gist.GistConnector; |
| import org.eclipse.mylyn.tasks.core.TaskRepository; |
| import org.eclipse.mylyn.tasks.ui.TasksUi; |
| import org.eclipse.mylyn.tasks.ui.TasksUiImages; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.ToolBar; |
| import org.eclipse.swt.widgets.ToolItem; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.dialogs.PatternFilter; |
| import org.eclipse.ui.model.WorkbenchAdapter; |
| import org.eclipse.ui.model.WorkbenchContentProvider; |
| import org.eclipse.ui.model.WorkbenchLabelProvider; |
| |
| /** |
| * Repository selection wizard page. |
| */ |
| public class RepositorySelectionWizardPage extends WizardPage { |
| |
| private static class RepositoryStyledLabelProvider implements |
| IStyledLabelProvider, ILabelProvider { |
| private final WorkbenchLabelProvider wrapped = new WorkbenchLabelProvider(); |
| |
| @Override |
| public String getText(Object element) { |
| return wrapped.getText(element); |
| } |
| |
| @Override |
| public void addListener(ILabelProviderListener listener) { |
| // empty |
| } |
| |
| @Override |
| public void dispose() { |
| wrapped.dispose(); |
| } |
| |
| @Override |
| public boolean isLabelProperty(Object element, String property) { |
| return false; |
| } |
| |
| @Override |
| public void removeListener(ILabelProviderListener listener) { |
| // empty |
| } |
| |
| @Override |
| public StyledString getStyledText(Object element) { |
| // TODO Replace with use of IWorkbenchAdapter3 when 3.6 is no longer |
| // supported |
| if (element instanceof RepositoryAdapter) |
| return ((RepositoryAdapter) element).getStyledText(element); |
| if (element instanceof OrganizationAdapter) |
| return ((OrganizationAdapter) element).getStyledText(element); |
| |
| return new StyledString(wrapped.getText(element)); |
| } |
| |
| @Override |
| public Image getImage(Object element) { |
| return wrapped.getImage(element); |
| } |
| |
| } |
| |
| private static class RepositoryLabelProvider extends |
| DelegatingStyledCellLabelProvider implements ILabelProvider { |
| |
| private final RepositoryStyledLabelProvider wrapped; |
| |
| public RepositoryLabelProvider() { |
| super(new RepositoryStyledLabelProvider()); |
| wrapped = (RepositoryStyledLabelProvider) getStyledStringProvider(); |
| } |
| |
| @Override |
| public String getText(Object element) { |
| return wrapped.getText(element); |
| } |
| |
| @Override |
| public void dispose() { |
| super.dispose(); |
| wrapped.dispose(); |
| } |
| } |
| |
| private static class RepositoryAdapter extends WorkbenchAdapter { |
| |
| private final Repository repo; |
| |
| RepositoryAdapter(Repository repo) { |
| this.repo = repo; |
| } |
| |
| @Override |
| public String getLabel(Object object) { |
| String label = this.repo.generateId(); |
| return label != null ? label : ""; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public ImageDescriptor getImageDescriptor(Object object) { |
| return TasksUiImages.REPOSITORY; |
| } |
| |
| } |
| |
| private static class OrganizationAdapter extends WorkbenchAdapter { |
| |
| private final User org; |
| |
| private final RepositoryAdapter[] repos; |
| |
| OrganizationAdapter(User org, List<Repository> repos) { |
| this.org = org; |
| this.repos = new RepositoryAdapter[repos.size()]; |
| final int length = this.repos.length; |
| for (int i = 0; i < length; i++) |
| this.repos[i] = new RepositoryAdapter(repos.get(i)); |
| } |
| |
| @Override |
| public ImageDescriptor getImageDescriptor(Object object) { |
| return GitHubImages.DESC_GITHUB_ORG; |
| } |
| |
| @Override |
| public Object[] getChildren(Object object) { |
| return repos; |
| } |
| |
| @Override |
| public StyledString getStyledText(Object object) { |
| StyledString styled = new StyledString(getLabel(object)); |
| styled.append( |
| MessageFormat.format(" ({0})", //$NON-NLS-1$ |
| Integer.valueOf(repos.length)), |
| StyledString.COUNTER_STYLER); |
| return styled; |
| } |
| |
| @Override |
| public String getLabel(Object object) { |
| return org.getLogin(); |
| } |
| |
| } |
| |
| private Button addGistRepoButton; |
| private Label selectedLabel; |
| private FilteredCheckboxTree tree; |
| private int repoCount = 0; |
| private String user; |
| private String password; |
| |
| private boolean isToken; |
| |
| /** |
| * Create repository selection wizard page |
| */ |
| public RepositorySelectionWizardPage() { |
| super( |
| "repositoriesPage", Messages.RepositorySelectionWizardPage_Title, null); //$NON-NLS-1$ |
| setDescription(Messages.RepositorySelectionWizardPage_Description); |
| } |
| |
| /** @param user */ |
| public void setUser(String user) { |
| this.user = user; |
| } |
| |
| /** @param password */ |
| public void setPassword(String password) { |
| this.password = password; |
| } |
| |
| /** |
| * Sets whether the {@link #setPassword(String) password} is a token. |
| * |
| * @param isToken |
| * whether the password is a token |
| */ |
| public void setIsToken(boolean isToken) { |
| this.isToken = isToken; |
| } |
| |
| /** @return true to create gists repository, false otherwise */ |
| public boolean createGistRepository() { |
| return this.addGistRepoButton.getSelection() |
| && this.addGistRepoButton.isVisible(); |
| } |
| |
| /** @return array of selected repositories */ |
| public Repository[] getRepositories() { |
| Object[] checked = tree.getCheckboxTreeViewer() |
| .getCheckedLeafElements(); |
| Repository[] repos = new Repository[checked.length]; |
| for (int i = 0; i < repos.length; i++) |
| repos[i] = ((RepositoryAdapter) checked[i]).repo; |
| return repos; |
| } |
| |
| /** @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) */ |
| @Override |
| public void createControl(Composite parent) { |
| Composite displayArea = new Composite(parent, SWT.NONE); |
| GridLayoutFactory.swtDefaults().numColumns(2).applyTo(displayArea); |
| |
| Label repoLabel = new Label(displayArea, SWT.NONE); |
| repoLabel.setText(Messages.RepositorySelectionWizardPage_LabelRepos); |
| GridDataFactory.fillDefaults().grab(true, false).span(2, 1) |
| .applyTo(repoLabel); |
| |
| tree = new FilteredCheckboxTree(displayArea, null, SWT.V_SCROLL |
| | SWT.H_SCROLL | SWT.BORDER, new PatternFilter()); |
| CheckboxTreeViewer viewer = tree.getCheckboxTreeViewer(); |
| viewer.setContentProvider(new WorkbenchContentProvider()); |
| viewer.setLabelProvider(new RepositoryLabelProvider()); |
| viewer.setComparator(new ViewerComparator() { |
| |
| @Override |
| public int compare(Viewer v, Object e1, Object e2) { |
| if (e1 instanceof OrganizationAdapter) |
| if (e2 instanceof OrganizationAdapter) |
| return ((OrganizationAdapter) e1) |
| .getLabel(e1) |
| .compareToIgnoreCase( |
| ((OrganizationAdapter) e2).getLabel(e2)); |
| else if (e2 instanceof RepositoryAdapter) |
| return 1; |
| if (e1 instanceof RepositoryAdapter) |
| if (e2 instanceof RepositoryAdapter) |
| return ((RepositoryAdapter) e1).getLabel(e1) |
| .compareToIgnoreCase( |
| ((RepositoryAdapter) e2).getLabel(e2)); |
| else if (e2 instanceof OrganizationAdapter) |
| return -1; |
| return super.compare(v, e1, e2); |
| } |
| |
| }); |
| viewer.addCheckStateListener(new ICheckStateListener() { |
| |
| @Override |
| public void checkStateChanged(CheckStateChangedEvent event) { |
| updateSelectionLabel(); |
| } |
| }); |
| GridDataFactory.fillDefaults().grab(true, true).applyTo(tree); |
| |
| ToolBar toolbar = new ToolBar(displayArea, SWT.FLAT | SWT.VERTICAL); |
| GridDataFactory.fillDefaults().grab(false, true).applyTo(toolbar); |
| ToolItem checkItem = new ToolItem(toolbar, SWT.PUSH); |
| checkItem.setImage(GitHubImages.get(GitHubImages.GITHUB_CHECKALL_OBJ)); |
| checkItem |
| .setToolTipText(Messages.RepositorySelectionWizardPage_TooltipCheckAll); |
| checkItem.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| tree.getCheckboxTreeViewer().setAllChecked(true); |
| for (Object leaf : tree.getCheckboxTreeViewer() |
| .getCheckedLeafElements()) |
| tree.getCheckboxTreeViewer().setChecked(leaf, true); |
| updateSelectionLabel(); |
| } |
| }); |
| ToolItem uncheckItem = new ToolItem(toolbar, SWT.PUSH); |
| uncheckItem.setImage(GitHubImages |
| .get(GitHubImages.GITHUB_UNCHECKALL_OBJ)); |
| uncheckItem |
| .setToolTipText(Messages.RepositorySelectionWizardPage_TooltipUncheckAll); |
| uncheckItem.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| tree.getCheckboxTreeViewer().setCheckedElements(new Object[0]); |
| updateSelectionLabel(); |
| } |
| }); |
| |
| selectedLabel = new Label(displayArea, SWT.NONE); |
| GridDataFactory.fillDefaults().grab(true, false).span(2, 1) |
| .applyTo(selectedLabel); |
| |
| addGistRepoButton = new Button(displayArea, SWT.CHECK); |
| addGistRepoButton |
| .setText(Messages.RepositorySelectionWizardPage_LabelAddGist); |
| addGistRepoButton.setSelection(true); |
| GridDataFactory.swtDefaults().span(2, 1).applyTo(addGistRepoButton); |
| addGistRepoButton.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| validatePage(); |
| } |
| |
| }); |
| |
| setControl(displayArea); |
| setPageComplete(false); |
| } |
| |
| private void updateSelectionLabel() { |
| selectedLabel.setText(MessageFormat.format( |
| Messages.RepositorySelectionWizardPage_LabelSelectionCount, |
| Integer.valueOf( |
| tree.getCheckboxTreeViewer().getCheckedLeafCount()), |
| Integer.valueOf(repoCount))); |
| selectedLabel.getParent().layout(true, true); |
| validatePage(); |
| } |
| |
| private void validatePage() { |
| setPageComplete(getRepositories().length > 0 || createGistRepository()); |
| if (isPageComplete()) |
| setErrorMessage(null); |
| } |
| |
| private void updateInput(final List<Object> repos) { |
| PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { |
| |
| @Override |
| public void run() { |
| if (getControl().isDisposed()) |
| return; |
| tree.getCheckboxTreeViewer().setCheckedElements(new Object[0]); |
| tree.getViewer().setInput(new WorkbenchAdapter() { |
| |
| @Override |
| public Object[] getChildren(Object object) { |
| return repos.toArray(); |
| } |
| |
| }); |
| updateSelectionLabel(); |
| } |
| }); |
| } |
| |
| private void removeExisting(List<Repository> repos, List<String> existing) { |
| Iterator<Repository> iter = repos.iterator(); |
| while (iter.hasNext()) { |
| String id = iter.next().generateId(); |
| if (id == null || existing.contains(id)) |
| iter.remove(); |
| } |
| } |
| |
| /** @see org.eclipse.jface.dialogs.DialogPage#setVisible(boolean) */ |
| @Override |
| public void setVisible(boolean visible) { |
| super.setVisible(visible); |
| if (!visible) { |
| return; |
| } |
| // For gists a user name is needed. |
| addGistRepoButton.setVisible(user != null && !user.isEmpty() |
| && TasksUi.getRepositoryManager() |
| .getRepositories(GistConnector.KIND).isEmpty()); |
| try { |
| getContainer().run(true, true, new IRunnableWithProgress() { |
| |
| @Override |
| public void run(IProgressMonitor monitor) |
| throws InvocationTargetException, InterruptedException { |
| GitHubClient client = GitHub |
| .configureClient(new GitHubClient()); |
| if (isToken) { |
| client.setOAuth2Token(password); |
| } else { |
| client.setCredentials(user, password); |
| } |
| RepositoryService service = new RepositoryService(client); |
| OrganizationService orgs = new OrganizationService(client); |
| repoCount = 0; |
| List<Object> repos = new ArrayList<>(); |
| List<String> existing = new ArrayList<>(); |
| for (TaskRepository repo : TasksUi.getRepositoryManager() |
| .getRepositories(GitHub.CONNECTOR_KIND)) { |
| String id = GitHub.getRepository( |
| repo.getRepositoryUrl()).generateId(); |
| if (id != null) |
| existing.add(id); |
| } |
| try { |
| monitor.beginTask("", 2); //$NON-NLS-1$ |
| monitor.setTaskName(Messages.RepositorySelectionWizardPage_TaskFetchingRepositories); |
| List<Repository> userRepos = service.getRepositories(); |
| removeExisting(userRepos, existing); |
| repoCount += userRepos.size(); |
| for (Repository repo : userRepos) |
| repos.add(new RepositoryAdapter(repo)); |
| monitor.worked(1); |
| monitor.setTaskName(Messages.RepositorySelectionWizardPage_TaskFetchingOrganizationRepositories); |
| for (User org : orgs.getOrganizations()) { |
| List<Repository> orgRepos = service |
| .getOrgRepositories(org.getLogin()); |
| removeExisting(orgRepos, existing); |
| repoCount += orgRepos.size(); |
| repos.add(new OrganizationAdapter(org, orgRepos)); |
| } |
| updateInput(repos); |
| } catch (IOException e) { |
| throw new InvocationTargetException(GitHubException |
| .wrap(e)); |
| } |
| } |
| }); |
| setErrorMessage(null); |
| } catch (InvocationTargetException e) { |
| updateInput(Collections.emptyList()); |
| Throwable cause = e.getCause(); |
| if (cause == null) |
| cause = e; |
| setErrorMessage(MessageFormat.format( |
| Messages.RepositorySelectionWizardPage_ErrorLoading, |
| cause.getLocalizedMessage())); |
| } catch (InterruptedException ignored) { |
| // Ignored |
| } |
| } |
| } |