| /******************************************************************************* |
| * Copyright (c) 2004, 2013 IBM Corporation and others. |
| * Copyright (C) 2007, Martin Oberhuber (martin.oberhuber@windriver.com) |
| * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> |
| * Copyright (C) 2009, Mykola Nikishov <mn@mn.com.ua> |
| * Copyright (C) 2010, 2017 Wim Jongman <wim.jongman@remainsoftware.com> |
| * Copyright (C) 2010, Ryan Schmitt <ryan.schmitt@boeing.com> |
| * Copyright (C) 2013, Robin Stocker <robin@nibor.org> |
| * |
| * 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 |
| *******************************************************************************/ |
| |
| package org.eclipse.egit.ui.internal.clone; |
| |
| import java.io.File; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.egit.core.internal.util.ProjectUtil; |
| import org.eclipse.egit.ui.Activator; |
| import org.eclipse.egit.ui.internal.GitLabelProvider; |
| import org.eclipse.egit.ui.internal.UIText; |
| import org.eclipse.egit.ui.internal.components.CachedCheckboxTreeViewer; |
| import org.eclipse.egit.ui.internal.components.FilteredCheckboxTree; |
| import org.eclipse.jface.dialogs.Dialog; |
| import org.eclipse.jface.dialogs.IDialogSettings; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.viewers.CheckStateChangedEvent; |
| import org.eclipse.jface.viewers.ICheckStateListener; |
| import org.eclipse.jface.viewers.IColorProvider; |
| import org.eclipse.jface.viewers.ITreeContentProvider; |
| import org.eclipse.jface.viewers.TreeViewer; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.jface.viewers.ViewerComparator; |
| import org.eclipse.jface.wizard.IWizard; |
| import org.eclipse.jface.wizard.WizardPage; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.TreeItem; |
| import org.eclipse.ui.IWorkingSet; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.dialogs.PatternFilter; |
| import org.eclipse.ui.dialogs.WorkingSetGroup; |
| |
| /** |
| * The GitWizardProjectsImportPage is the page that allows the user to import |
| * projects from a particular location. This is a modified copy of the |
| * WizardProjectsImportPage class from the org.eclipse.ui.ide bundle. |
| */ |
| public class GitProjectsImportPage extends WizardPage { |
| |
| private final class ProjectLabelProvider extends GitLabelProvider implements |
| IColorProvider { |
| |
| @Override |
| public Color getForeground(Object element) { |
| if (isProjectInWorkspace(((ProjectRecord) element).getProjectName())) |
| return PlatformUI.getWorkbench().getDisplay().getSystemColor( |
| SWT.COLOR_GRAY); |
| return null; |
| } |
| |
| @Override |
| public Color getBackground(Object element) { |
| return null; |
| } |
| } |
| |
| private final static String STORE_NESTED_PROJECTS = "GitProjectsImportPage.STORE_NESTED_PROJECTS"; //$NON-NLS-1$ |
| |
| /** |
| * The name of the folder containing metadata information for the workspace. |
| */ |
| public static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$ |
| |
| private CachedCheckboxTreeViewer projectsList; |
| |
| private Button nestedProjectsCheckbox; |
| |
| private boolean nestedProjects = true; |
| |
| private boolean lastNestedProjects = true; |
| |
| private ProjectRecord[] selectedProjects = new ProjectRecord[0]; |
| |
| private IProject[] wsProjects; |
| |
| // The last selected path to minimize searches |
| private String lastPath; |
| |
| // The last time that the file or folder at the selected path was modified |
| // to minimize searches |
| private long lastModified; |
| |
| private Button selectAll; |
| |
| private Button deselectAll; |
| |
| private WorkingSetGroup workingSetGroup; |
| |
| /** |
| * Creates a new project creation wizard page. |
| */ |
| public GitProjectsImportPage() { |
| super(GitProjectsImportPage.class.getName()); |
| setPageComplete(false); |
| setTitle(UIText.WizardProjectsImportPage_ImportProjectsTitle); |
| setDescription(UIText.WizardProjectsImportPage_ImportProjectsDescription); |
| } |
| |
| @Override |
| public void createControl(Composite parent) { |
| |
| initializeDialogUnits(parent); |
| |
| Composite workArea = new Composite(parent, SWT.NONE); |
| setControl(workArea); |
| |
| workArea.setLayout(GridLayoutFactory.fillDefaults().create()); |
| workArea.setLayoutData(new GridData(GridData.FILL_BOTH |
| | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL)); |
| |
| createProjectsRoot(workArea); |
| createProjectsList(workArea); |
| createOptionsArea(workArea); |
| createWorkingSetGroup(workArea); |
| restoreWidgetValues(); |
| Dialog.applyDialogFont(workArea); |
| |
| } |
| |
| private void createWorkingSetGroup(Composite workArea) { |
| // TODO: replace hardcoded ids once bug 245106 is fixed |
| String[] workingSetTypes = new String[] { |
| "org.eclipse.ui.resourceWorkingSetPage", //$NON-NLS-1$ |
| "org.eclipse.jdt.ui.JavaWorkingSetPage" //$NON-NLS-1$ |
| }; |
| workingSetGroup = new WorkingSetGroup(workArea, null, workingSetTypes); |
| } |
| |
| /** |
| * Create the checkbox list for the found projects. |
| * |
| * @param workArea |
| */ |
| private void createProjectsList(Composite workArea) { |
| Label title = new Label(workArea, SWT.NONE); |
| title.setText(UIText.WizardProjectsImportPage_ProjectsListTitle); |
| |
| Composite listComposite = new Composite(workArea, SWT.NONE); |
| GridLayout layout = new GridLayout(); |
| layout.numColumns = 2; |
| layout.marginWidth = 0; |
| layout.makeColumnsEqualWidth = false; |
| listComposite.setLayout(layout); |
| |
| listComposite.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL |
| | GridData.GRAB_VERTICAL | GridData.FILL_BOTH)); |
| |
| PatternFilter filter = new PatternFilter() { |
| |
| @Override |
| public boolean isElementVisible(Viewer viewer, Object element) { |
| |
| if (getCheckedProjects().contains(element)) |
| return true; |
| |
| if (element instanceof ProjectRecord) { |
| ProjectRecord p = (ProjectRecord) element; |
| if (wordMatches(p.getProjectName())) |
| return true; |
| String projectPath = p.getProjectSystemFile().getParent(); |
| if (projectPath.startsWith(lastPath)) { |
| String distinctPath = projectPath.substring(lastPath |
| .length()); |
| return wordMatches(distinctPath); |
| } else { |
| return wordMatches(projectPath); |
| } |
| } |
| |
| return false; |
| } |
| }; |
| filter.setIncludeLeadingWildcard(true); |
| |
| FilteredCheckboxTree filteredTree = new FilteredCheckboxTree( |
| listComposite, null, SWT.NONE, filter); |
| |
| filteredTree.setInitialText(UIText.WizardProjectsImportPage_filterText); |
| projectsList = filteredTree.getCheckboxTreeViewer(); |
| GridData listData = new GridData(GridData.GRAB_HORIZONTAL |
| | GridData.GRAB_VERTICAL | GridData.FILL_BOTH); |
| projectsList.getControl().setLayoutData(listData); |
| projectsList.addCheckStateListener(new ICheckStateListener() { |
| |
| @Override |
| public void checkStateChanged(CheckStateChangedEvent event) { |
| ProjectRecord element = (ProjectRecord) event.getElement(); |
| if (isProjectInWorkspace(element.getProjectName())) { |
| projectsList.setChecked(element, false); |
| } |
| enableSelectAllButtons(); |
| } |
| }); |
| |
| // a bug in the CachedCheckboxTreeView requires us to not return null |
| final Object[] children = new Object[0]; |
| |
| projectsList.setContentProvider(new ITreeContentProvider() { |
| |
| @Override |
| public Object[] getChildren(Object parentElement) { |
| return children; |
| } |
| |
| @Override |
| public Object[] getElements(Object inputElement) { |
| return selectedProjects; |
| } |
| |
| @Override |
| public boolean hasChildren(Object element) { |
| return false; |
| } |
| |
| @Override |
| public Object getParent(Object element) { |
| return null; |
| } |
| |
| @Override |
| public void dispose() { |
| // ignore |
| } |
| |
| @Override |
| public void inputChanged(Viewer viewer, Object oldInput, |
| Object newInput) { |
| // ignore |
| } |
| |
| }); |
| |
| projectsList.getTree().addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| checkPageComplete(); |
| } |
| }); |
| |
| projectsList.setLabelProvider(new ProjectLabelProvider()); |
| |
| projectsList.setInput(this); |
| projectsList.setComparator(new ViewerComparator()); |
| createSelectionButtons(listComposite); |
| } |
| |
| /** |
| * Create the selection buttons in the listComposite. |
| * |
| * @param listComposite |
| */ |
| private void createSelectionButtons(Composite listComposite) { |
| Composite buttonsComposite = new Composite(listComposite, SWT.NONE); |
| GridLayout layout = new GridLayout(); |
| layout.marginWidth = 0; |
| layout.marginHeight = 0; |
| buttonsComposite.setLayout(layout); |
| |
| buttonsComposite.setLayoutData(new GridData( |
| GridData.VERTICAL_ALIGN_BEGINNING)); |
| |
| selectAll = new Button(buttonsComposite, SWT.PUSH); |
| selectAll.setText(UIText.WizardProjectsImportPage_selectAll); |
| selectAll.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| selectAllNewProjects(); |
| enableSelectAllButtons(); |
| setPageComplete(true); |
| } |
| }); |
| Dialog.applyDialogFont(selectAll); |
| setButtonLayoutData(selectAll); |
| |
| deselectAll = new Button(buttonsComposite, SWT.PUSH); |
| deselectAll.setText(UIText.WizardProjectsImportPage_deselectAll); |
| deselectAll.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| for (TreeItem item : projectsList.getTree().getItems()) |
| projectsList.setChecked(item.getData(), false); |
| projectsList.setInput(this); // filter away selected projects |
| enableSelectAllButtons(); |
| setPageComplete(false); |
| } |
| }); |
| Dialog.applyDialogFont(deselectAll); |
| setButtonLayoutData(deselectAll); |
| } |
| |
| private void createOptionsArea(Composite workArea) { |
| Composite optionsGroup = new Composite(workArea, SWT.NONE); |
| GridLayout layout = new GridLayout(); |
| layout.marginBottom = 2 * layout.marginHeight; |
| layout.marginHeight = 0; |
| optionsGroup.setLayout(layout); |
| optionsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); |
| |
| nestedProjectsCheckbox = new Button(optionsGroup, SWT.CHECK); |
| nestedProjectsCheckbox |
| .setText(UIText.GitProjectsImportPage_SearchForNestedProjects); |
| nestedProjectsCheckbox.setLayoutData(new GridData( |
| GridData.FILL_HORIZONTAL)); |
| nestedProjectsCheckbox.setSelection(nestedProjects); |
| nestedProjectsCheckbox.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| nestedProjects = nestedProjectsCheckbox.getSelection(); |
| setProjectsList(lastPath); |
| } |
| }); |
| } |
| |
| private void selectAllNewProjects() { |
| for (TreeItem item : projectsList.getTree().getItems()) { |
| ProjectRecord record = (ProjectRecord) item.getData(); |
| if (!isProjectInWorkspace(record.getProjectName())) |
| projectsList.setChecked(item.getData(), true); |
| } |
| } |
| |
| /** |
| * Create the area where you select the root directory for the projects. |
| * |
| * @param workArea |
| * Composite |
| */ |
| private void createProjectsRoot(Composite workArea) { |
| |
| // project specification group |
| Composite projectGroup = new Composite(workArea, SWT.NONE); |
| GridLayout layout = new GridLayout(); |
| layout.numColumns = 3; |
| layout.makeColumnsEqualWidth = false; |
| layout.marginWidth = 0; |
| projectGroup.setLayout(layout); |
| projectGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); |
| } |
| |
| /** |
| * Update the list of projects based on path. This will not check any |
| * projects. |
| * |
| * @param path |
| */ |
| void setProjectsList(final String path) { |
| // on an empty path empty selectedProjects |
| if (path == null || path.length() == 0) { |
| selectedProjects = new ProjectRecord[0]; |
| projectsList.refresh(true); |
| checkPageComplete(); |
| lastPath = path; |
| setErrorMessage(UIText.GitProjectsImportPage_NoProjectsMessage); |
| return; |
| } |
| |
| final File directory = new File(path); |
| long modified = directory.lastModified(); |
| if (path.equals(lastPath) && lastModified == modified |
| && lastNestedProjects == nestedProjects) { |
| // since the file/folder was not modified and the path did not |
| // change, no refreshing is required |
| return; |
| } |
| |
| setErrorMessage(null); |
| |
| lastPath = path; |
| lastModified = modified; |
| lastNestedProjects = nestedProjects; |
| |
| try { |
| getContainer().run(true, true, new IRunnableWithProgress() { |
| |
| @Override |
| public void run(IProgressMonitor monitor) { |
| selectedProjects = new ProjectRecord[0]; |
| Collection<File> files = new ArrayList<>(); |
| if (directory.isDirectory()) { |
| SubMonitor progress = SubMonitor.convert(monitor, 2); |
| progress.setTaskName( |
| UIText.WizardProjectsImportPage_SearchingMessage); |
| boolean searchNested = nestedProjects; |
| |
| boolean found = ProjectUtil.findProjectFiles(files, |
| directory, searchNested, progress.newChild(1)); |
| |
| if (!found) { |
| return; |
| } |
| ArrayList<ProjectRecord> result = new ArrayList<>(); |
| progress.setWorkRemaining(files.size()); |
| progress.subTask( |
| UIText.WizardProjectsImportPage_ProcessingMessage); |
| for (File file : files) { |
| if (isSelected(file)) { |
| ProjectRecord record = new ProjectRecord(file); |
| if (record.getProjectDescription() == null) { |
| // Ignore invalid .project files |
| continue; |
| } |
| result.add(record); |
| } |
| progress.worked(1); |
| } |
| selectedProjects = result |
| .toArray(new ProjectRecord[0]); |
| |
| if (selectedProjects.length == 0) { |
| // run in UI thread |
| PlatformUI.getWorkbench().getDisplay().syncExec(() -> setErrorMessage( |
| UIText.GitProjectsImportPage_NoProjectsMessage)); |
| } |
| } |
| } |
| |
| private boolean isSelected(File pFile) { |
| IWizard wiz = getWizard(); |
| if (!(wiz instanceof GitCreateProjectViaWizardWizard)) { |
| return true; |
| } |
| GitCreateProjectViaWizardWizard wizard = (GitCreateProjectViaWizardWizard) wiz; |
| return wizard.getFilter().isEmpty() || wizard.getFilter().contains(pFile.getParent()); |
| } |
| |
| }); |
| } catch (InvocationTargetException e) { |
| Activator.logError(e.getMessage(), e); |
| } catch (InterruptedException e) { |
| // Nothing to do if the user interrupts. |
| } |
| |
| projectsList.refresh(true); |
| if (getValidProjects().length < selectedProjects.length) { |
| setMessage(UIText.WizardProjectsImportPage_projectsInWorkspace, |
| WARNING); |
| } else { |
| setMessage(UIText.WizardProjectsImportPage_ImportProjectsDescription); |
| } |
| selectAllNewProjects(); |
| enableSelectAllButtons(); |
| checkPageComplete(); |
| } |
| |
| private void enableSelectAllButtons() { |
| int itemCount = getValidProjects().length; |
| int selectionCount = projectsList.getCheckedLeafCount(); |
| selectAll.setEnabled(itemCount > selectionCount && itemCount > 0); |
| deselectAll.setEnabled(selectionCount > 0); |
| } |
| |
| /** |
| * Method used for test suite. |
| * |
| * @return CheckboxTreeViewer the viewer containing all the projects found |
| */ |
| public TreeViewer getProjectsList() { |
| return projectsList; |
| } |
| |
| /** |
| * Retrieve all the projects in the current workspace. |
| * |
| * @return IProject[] array of IProject in the current workspace |
| */ |
| private IProject[] getProjectsInWorkspace() { |
| if (wsProjects == null) { |
| wsProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); |
| } |
| return wsProjects; |
| } |
| |
| /** |
| * Get the array of valid project records that can be imported from the |
| * source workspace or archive, selected by the user. If a project with the |
| * same name exists in both the source workspace and the current workspace, |
| * it will not appear in the list of projects to import and thus cannot be |
| * selected for import. |
| * |
| * Method declared public for test suite. |
| * |
| * @return ProjectRecord[] array of projects that can be imported into the |
| * workspace |
| */ |
| public ProjectRecord[] getValidProjects() { |
| List<ProjectRecord> validProjects = new ArrayList<>(); |
| for (ProjectRecord selectedProject : selectedProjects) { |
| if (!isProjectInWorkspace(selectedProject.getProjectName())) { |
| validProjects.add(selectedProject); |
| } |
| } |
| return validProjects.toArray(new ProjectRecord[0]); |
| } |
| |
| /** |
| * Determine if the project with the given name is in the current workspace. |
| * |
| * @param projectName |
| * String the project name to check |
| * @return boolean true if the project with the given name is in this |
| * workspace |
| */ |
| private boolean isProjectInWorkspace(String projectName) { |
| if (projectName == null) { |
| return false; |
| } |
| IProject[] workspaceProjects = getProjectsInWorkspace(); |
| for (IProject workspaceProject : workspaceProjects) { |
| if (projectName.equals(workspaceProject.getName())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @return All the currently checked projects in the projectsList tree |
| */ |
| public Set<ProjectRecord> getCheckedProjects() { |
| HashSet<ProjectRecord> ret = new HashSet<>(); |
| for (Object selected : projectsList.getCheckedElements()) |
| ret.add((ProjectRecord) selected); |
| |
| return ret; |
| } |
| |
| /** |
| * @return the selected working sets (may be empty) |
| */ |
| public IWorkingSet[] getSelectedWorkingSets() { |
| return workingSetGroup.getSelectedWorkingSets(); |
| } |
| |
| private void checkPageComplete() { |
| setPageComplete(!getCheckedProjects().isEmpty()); |
| } |
| |
| private void restoreWidgetValues() { |
| IDialogSettings settings = getDialogSettings(); |
| if (settings != null && settings.get(STORE_NESTED_PROJECTS) != null) { |
| nestedProjects = settings.getBoolean(STORE_NESTED_PROJECTS); |
| nestedProjectsCheckbox.setSelection(nestedProjects); |
| lastNestedProjects = nestedProjects; |
| } |
| } |
| |
| void saveWidgetValues() { |
| IDialogSettings settings = getDialogSettings(); |
| if (settings != null) |
| settings.put(STORE_NESTED_PROJECTS, |
| nestedProjectsCheckbox.getSelection()); |
| } |
| } |