| /******************************************************************************* |
| * Copyright (c) 2010 SAP AG. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Mathias Kinzler (SAP AG) - initial implementation |
| *******************************************************************************/ |
| package org.eclipse.egit.ui.internal.repository; |
| |
| import java.io.File; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.core.runtime.preferences.InstanceScope; |
| import org.eclipse.egit.core.Activator; |
| import org.eclipse.egit.ui.UIUtils; |
| import org.eclipse.egit.ui.internal.UIIcons; |
| 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.IMessageProvider; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.resource.LocalResourceManager; |
| import org.eclipse.jface.resource.ResourceManager; |
| 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.LabelProvider; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.jface.wizard.WizardPage; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.RepositoryCache.FileKey; |
| import org.eclipse.jgit.util.FS; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.ModifyEvent; |
| import org.eclipse.swt.events.ModifyListener; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Image; |
| 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.DirectoryDialog; |
| import org.eclipse.swt.widgets.Group; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Text; |
| 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.osgi.service.prefs.BackingStoreException; |
| |
| /** |
| * Searches for Git directories under a path that can be selected by the user |
| */ |
| public class RepositorySearchDialog extends WizardPage { |
| |
| private static final String PREF_DEEP_SEARCH = "RepositorySearchDialogDeepSearch"; //$NON-NLS-1$ |
| |
| private static final String PREF_PATH = "RepositorySearchDialogSearchPath"; //$NON-NLS-1$ |
| |
| private final Set<String> fExistingDirectories = new HashSet<String>(); |
| |
| private final boolean fillSearch; |
| |
| private Set<String> fResult; |
| |
| private FilteredCheckboxTree fTree; |
| |
| private CachedCheckboxTreeViewer fTreeViewer; |
| |
| private Text dir; |
| |
| private Button lookForNestedButton; |
| |
| private Button searchButton; |
| |
| private ToolItem checkAllItem; |
| |
| private ToolItem uncheckAllItem; |
| |
| private final ResourceManager fImageCache = new LocalResourceManager( |
| JFaceResources.getResources()); |
| |
| private final IEclipsePreferences prefs = InstanceScope.INSTANCE |
| .getNode(Activator.getPluginId()); |
| |
| private static final class ContentProvider implements ITreeContentProvider { |
| |
| private final Object[] children = new Object[0]; |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public Object[] getElements(Object inputElement) { |
| return ((Set<String>) inputElement).toArray(); |
| } |
| |
| @Override |
| public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { |
| // nothing |
| } |
| |
| @Override |
| public void dispose() { |
| // nothing |
| } |
| |
| @Override |
| public Object[] getChildren(Object parentElement) { |
| // do not return null due to a bug in FilteredTree |
| return children; |
| } |
| |
| @Override |
| public Object getParent(Object element) { |
| // nothing |
| return null; |
| } |
| |
| @Override |
| public boolean hasChildren(Object element) { |
| // nothing |
| return false; |
| } |
| |
| } |
| |
| private final class RepositoryLabelProvider extends LabelProvider implements |
| IColorProvider { |
| |
| @Override |
| public Image getImage(Object element) { |
| return fImageCache.createImage(UIIcons.REPOSITORY); |
| } |
| |
| @Override |
| public String getText(Object element) { |
| return element.toString(); |
| } |
| |
| @Override |
| public Color getBackground(Object element) { |
| return null; |
| } |
| |
| @Override |
| public Color getForeground(Object element) { |
| if (fExistingDirectories.contains(element)) |
| return getShell().getDisplay().getSystemColor(SWT.COLOR_GRAY); |
| |
| return null; |
| } |
| |
| @Override |
| public void dispose() { |
| fImageCache.dispose(); |
| } |
| |
| } |
| |
| /** |
| * @param existingDirs |
| */ |
| public RepositorySearchDialog(Collection<String> existingDirs) { |
| this(existingDirs, false); |
| } |
| |
| /** |
| * @param existingDirs |
| * @param fillSearch true to fill search results when initially displayed |
| */ |
| public RepositorySearchDialog(Collection<String> existingDirs, |
| boolean fillSearch) { |
| super( |
| "searchPage", UIText.RepositorySearchDialog_SearchTitle, UIIcons.WIZBAN_IMPORT_REPO); //$NON-NLS-1$ |
| this.fExistingDirectories.addAll(existingDirs); |
| this.fillSearch = fillSearch; |
| } |
| |
| /** |
| * |
| * @return the directories |
| */ |
| public Set<String> getDirectories() { |
| return fResult; |
| } |
| |
| @Override |
| public void dispose() { |
| fResult = getCheckedItems(); |
| super.dispose(); |
| } |
| |
| @Override |
| public void createControl(Composite parent) { |
| setMessage(UIText.RepositorySearchDialog_searchRepositoriesMessage); |
| |
| Composite main = new Composite(parent, SWT.NONE); |
| main.setLayout(new GridLayout(1, false)); |
| main.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| |
| Group searchGroup = new Group(main, SWT.SHADOW_ETCHED_IN); |
| searchGroup.setText(UIText.RepositorySearchDialog_SearchCriteriaGroup); |
| searchGroup.setLayout(new GridLayout(4, false)); |
| GridDataFactory.fillDefaults().grab(true, false).applyTo(searchGroup); |
| |
| Label dirLabel = new Label(searchGroup, SWT.NONE); |
| dirLabel.setText(UIText.RepositorySearchDialog_directory); |
| dir = new Text(searchGroup, SWT.BORDER); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, |
| false).hint(300, SWT.DEFAULT).applyTo(dir); |
| dir.setToolTipText(UIText.RepositorySearchDialog_EnterDirectoryToolTip); |
| |
| String defaultRepoPath = UIUtils.getDefaultRepositoryDir(); |
| |
| String initialPath = prefs.get(PREF_PATH, defaultRepoPath); |
| |
| dir.setText(initialPath); |
| |
| Button browse = new Button(searchGroup, SWT.PUSH); |
| browse.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, |
| 1, 1)); |
| browse.setText(UIText.RepositorySearchDialog_browse); |
| browse.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| DirectoryDialog dd = new DirectoryDialog(getShell()); |
| dd.setFilterPath(dir.getText()); |
| String directory = dd.open(); |
| if (directory != null) { |
| dir.setText(directory); |
| prefs.put(PREF_PATH, directory); |
| try { |
| prefs.flush(); |
| } catch (BackingStoreException e1) { |
| // ignore here |
| } |
| doSearch(); |
| } |
| } |
| |
| }); |
| |
| searchButton = new Button(searchGroup, SWT.PUSH); |
| searchButton.setText(UIText.RepositorySearchDialog_Search); |
| searchButton |
| .setToolTipText(UIText.RepositorySearchDialog_SearchTooltip); |
| searchButton.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| doSearch(); |
| } |
| }); |
| |
| lookForNestedButton = new Button(searchGroup, SWT.CHECK); |
| lookForNestedButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, |
| false, false, 4, 1)); |
| lookForNestedButton.setSelection(prefs.getBoolean(PREF_DEEP_SEARCH, |
| false)); |
| lookForNestedButton |
| .setText(UIText.RepositorySearchDialog_DeepSearch_button); |
| lookForNestedButton |
| .setToolTipText(UIText.RepositorySearchDialog_SearchRecursiveToolTip); |
| |
| lookForNestedButton.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| prefs.putBoolean(PREF_DEEP_SEARCH, lookForNestedButton |
| .getSelection()); |
| try { |
| prefs.flush(); |
| } catch (BackingStoreException e1) { |
| // ignore |
| } |
| setNeedsSearch(); |
| } |
| |
| }); |
| |
| Group searchResultGroup = new Group(main, SWT.SHADOW_ETCHED_IN); |
| searchResultGroup |
| .setText(UIText.RepositorySearchDialog_SearchResultGroup); |
| searchResultGroup.setLayout(new GridLayout(2, false)); |
| GridDataFactory.fillDefaults().applyTo(searchResultGroup); |
| |
| PatternFilter filter = new PatternFilter() { |
| |
| @Override |
| public boolean isElementVisible(Viewer viewer, Object element) { |
| if (getCheckedItems().contains(element)) { |
| return true; |
| } |
| return super.isElementVisible(viewer, element); |
| } |
| }; |
| |
| fTree = new FilteredCheckboxTree(searchResultGroup, null, SWT.NONE, |
| filter); |
| fTreeViewer = fTree.getCheckboxTreeViewer(); |
| fTreeViewer.addCheckStateListener(new ICheckStateListener() { |
| |
| @Override |
| public void checkStateChanged(CheckStateChangedEvent event) { |
| enableOk(); |
| } |
| }); |
| |
| GridDataFactory.fillDefaults().grab(true, true).minSize(0, 300) |
| .applyTo(fTree); |
| |
| ToolBar toolbar = new ToolBar(searchResultGroup, SWT.FLAT |
| | SWT.VERTICAL); |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).applyTo( |
| toolbar); |
| |
| checkAllItem = new ToolItem(toolbar, SWT.PUSH); |
| checkAllItem |
| .setToolTipText(UIText.RepositorySearchDialog_CheckAllRepositories); |
| checkAllItem.setEnabled(false); |
| Image checkImage = UIIcons.CHECK_ALL.createImage(); |
| UIUtils.hookDisposal(checkAllItem, checkImage); |
| checkAllItem.setImage(checkImage); |
| checkAllItem.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| fTreeViewer.setAllChecked(true); |
| enableOk(); |
| } |
| |
| }); |
| |
| uncheckAllItem = new ToolItem(toolbar, SWT.PUSH); |
| uncheckAllItem |
| .setToolTipText(UIText.RepositorySearchDialog_UncheckAllRepositories); |
| uncheckAllItem.setEnabled(false); |
| Image uncheckImage = UIIcons.UNCHECK_ALL.createImage(); |
| UIUtils.hookDisposal(uncheckAllItem, uncheckImage); |
| uncheckAllItem.setImage(uncheckImage); |
| uncheckAllItem.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| fTreeViewer.setAllChecked(false); |
| enableOk(); |
| } |
| |
| }); |
| |
| // TODO this isn't the most optimal way of handling this... ideally we |
| // should have some type of delay |
| // if we could use databinding an observeDelayedValue would totally work |
| // here |
| dir.addModifyListener(new ModifyListener() { |
| |
| @Override |
| public void modifyText(ModifyEvent e) { |
| setNeedsSearch(); |
| } |
| |
| }); |
| |
| fTreeViewer.setContentProvider(new ContentProvider()); |
| fTreeViewer.setLabelProvider(new RepositoryLabelProvider()); |
| |
| setControl(main); |
| |
| if (fillSearch) |
| PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { |
| |
| @Override |
| public void run() { |
| if (!getControl().isDisposed()) |
| doSearch(); |
| } |
| }); |
| } |
| |
| private void findGitDirsRecursive(File root, Set<String> strings, |
| IProgressMonitor monitor, boolean lookForNestedRepositories) { |
| |
| if (!root.exists() || !root.isDirectory()) { |
| return; |
| } |
| File[] children = root.listFiles(); |
| // simply ignore null |
| if (children == null) |
| return; |
| |
| for (File child : children) { |
| if (monitor.isCanceled()) |
| return; |
| if (!child.isDirectory()) |
| continue; |
| |
| if (FileKey.isGitRepository(child, FS.DETECTED)) { |
| strings.add(child.getAbsolutePath()); |
| monitor.setTaskName(NLS |
| .bind(UIText.RepositorySearchDialog_RepositoriesFound_message, |
| Integer.valueOf(strings.size()))); |
| } else if (FileKey.isGitRepository(new File(child, |
| Constants.DOT_GIT), FS.DETECTED)) { |
| strings.add(new File(child, Constants.DOT_GIT) |
| .getAbsolutePath()); |
| monitor.setTaskName(NLS |
| .bind(UIText.RepositorySearchDialog_RepositoriesFound_message, |
| Integer.valueOf(strings.size()))); |
| } else if (lookForNestedRepositories) { |
| monitor.subTask(child.getPath()); |
| findGitDirsRecursive(child, strings, monitor, |
| lookForNestedRepositories); |
| } |
| } |
| } |
| |
| private HashSet<String> getCheckedItems() { |
| HashSet<String> ret = new HashSet<String>(); |
| for (Object item : fTreeViewer.getCheckedLeafElements()) |
| ret.add((String) item); |
| return ret; |
| } |
| |
| private void doSearch() { |
| setMessage(UIText.RepositorySearchDialog_searchRepositoriesMessage); |
| setErrorMessage(null); |
| // perform the search... |
| final Set<String> directories = new HashSet<String>(); |
| final File file = new File(dir.getText()); |
| final boolean lookForNested = lookForNestedButton.getSelection(); |
| if(!file.exists()) |
| return; |
| |
| prefs.put(PREF_PATH, file.getAbsolutePath()); |
| try { |
| prefs.flush(); |
| } catch (BackingStoreException e1) { |
| // ignore here |
| } |
| |
| final TreeSet<String> validDirs = new TreeSet<String>(getCheckedItems()); |
| |
| IRunnableWithProgress action = new IRunnableWithProgress() { |
| |
| @Override |
| public void run(IProgressMonitor monitor) |
| throws InvocationTargetException, InterruptedException { |
| monitor.beginTask( |
| UIText.RepositorySearchDialog_ScanningForRepositories_message, |
| IProgressMonitor.UNKNOWN); |
| try { |
| findGitDirsRecursive(file, directories, monitor, |
| lookForNested); |
| } catch (Exception ex) { |
| throw new InvocationTargetException(ex); |
| } |
| if (monitor.isCanceled()) { |
| throw new InterruptedException(); |
| } |
| } |
| }; |
| try { |
| getContainer().run(true, true, action); |
| } catch (InvocationTargetException e1) { |
| org.eclipse.egit.ui.Activator.handleError( |
| UIText.RepositorySearchDialog_errorOccurred, e1.getCause(), |
| true); |
| } catch (InterruptedException e1) { |
| // ignore |
| } |
| |
| int foundOld = 0; |
| |
| for (String foundDir : directories) { |
| if (!fExistingDirectories.contains(foundDir)) { |
| validDirs.add(foundDir); |
| } else { |
| foundOld++; |
| } |
| } |
| |
| if (foundOld > 0) { |
| String message = NLS.bind( |
| UIText.RepositorySearchDialog_SomeDirectoriesHiddenMessage, |
| Integer.valueOf(foundOld)); |
| setMessage(message, IMessageProvider.INFORMATION); |
| } else if (directories.isEmpty()) |
| setMessage(UIText.RepositorySearchDialog_NothingFoundMessage, |
| IMessageProvider.INFORMATION); |
| |
| checkAllItem.setEnabled(!validDirs.isEmpty()); |
| uncheckAllItem.setEnabled(!validDirs.isEmpty()); |
| fTree.clearFilter(); |
| fTreeViewer.setInput(validDirs); |
| enableOk(); |
| } |
| |
| private void setNeedsSearch() { |
| fTreeViewer.setInput(null); |
| final File file = new File(dir.getText()); |
| if (!file.exists()) { |
| setErrorMessage(NLS.bind( |
| UIText.RepositorySearchDialog_DirectoryNotFoundMessage, dir |
| .getText())); |
| } else { |
| setErrorMessage(null); |
| setMessage(UIText.RepositorySearchDialog_NoSearchAvailableMessage, |
| IMessageProvider.INFORMATION); |
| } |
| enableOk(); |
| } |
| |
| private void enableOk() { |
| boolean enable = fTreeViewer.getCheckedElements().length > 0; |
| setPageComplete(enable); |
| } |
| } |