The "Add Repositories" dialog should not scan automatically
[egit.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / RepositorySearchDialog.java
blob685fff49cf9d0708dfa21fbd613605875786444b
1 /*******************************************************************************
2 * Copyright (c) 2010 SAP AG.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
8 * Contributors:
9 * Mathias Kinzler (SAP AG) - initial implementation
10 *******************************************************************************/
11 package org.eclipse.egit.ui.internal.repository;
13 import java.io.File;
14 import java.io.IOException;
15 import java.lang.reflect.InvocationTargetException;
16 import java.util.Collection;
17 import java.util.HashSet;
18 import java.util.Set;
19 import java.util.TreeSet;
21 import org.eclipse.core.runtime.IProgressMonitor;
22 import org.eclipse.core.runtime.IStatus;
23 import org.eclipse.core.runtime.Status;
24 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
25 import org.eclipse.core.runtime.preferences.InstanceScope;
26 import org.eclipse.egit.core.Activator;
27 import org.eclipse.egit.ui.UIIcons;
28 import org.eclipse.egit.ui.UIText;
29 import org.eclipse.jface.dialogs.IMessageProvider;
30 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
31 import org.eclipse.jface.dialogs.TitleAreaDialog;
32 import org.eclipse.jface.layout.GridDataFactory;
33 import org.eclipse.jface.operation.IRunnableWithProgress;
34 import org.eclipse.jface.resource.JFaceResources;
35 import org.eclipse.jface.resource.LocalResourceManager;
36 import org.eclipse.jface.resource.ResourceManager;
37 import org.eclipse.jface.viewers.IColorProvider;
38 import org.eclipse.jface.viewers.ISelectionChangedListener;
39 import org.eclipse.jface.viewers.ITreeContentProvider;
40 import org.eclipse.jface.viewers.LabelProvider;
41 import org.eclipse.jface.viewers.SelectionChangedEvent;
42 import org.eclipse.jface.viewers.TreeViewer;
43 import org.eclipse.jface.viewers.Viewer;
44 import org.eclipse.jgit.lib.RepositoryCache;
45 import org.eclipse.jgit.util.FS;
46 import org.eclipse.osgi.util.NLS;
47 import org.eclipse.swt.SWT;
48 import org.eclipse.swt.events.ModifyEvent;
49 import org.eclipse.swt.events.ModifyListener;
50 import org.eclipse.swt.events.SelectionAdapter;
51 import org.eclipse.swt.events.SelectionEvent;
52 import org.eclipse.swt.graphics.Color;
53 import org.eclipse.swt.graphics.Image;
54 import org.eclipse.swt.layout.GridData;
55 import org.eclipse.swt.layout.GridLayout;
56 import org.eclipse.swt.widgets.Button;
57 import org.eclipse.swt.widgets.Composite;
58 import org.eclipse.swt.widgets.Control;
59 import org.eclipse.swt.widgets.DirectoryDialog;
60 import org.eclipse.swt.widgets.Group;
61 import org.eclipse.swt.widgets.Label;
62 import org.eclipse.swt.widgets.Shell;
63 import org.eclipse.swt.widgets.Text;
64 import org.eclipse.swt.widgets.TreeItem;
65 import org.eclipse.ui.dialogs.FilteredTree;
66 import org.eclipse.ui.dialogs.PatternFilter;
67 import org.osgi.service.prefs.BackingStoreException;
69 /**
70 * Searches for Git directories under a path that can be selected by the user
72 public class RepositorySearchDialog extends TitleAreaDialog {
74 private static final String PREF_DEEP_SEARCH = "RepositorySearchDialogDeepSearch"; //$NON-NLS-1$
76 private static final String PREF_PATH = "RepositorySearchDialogSearchPath"; //$NON-NLS-1$
78 private final Set<String> fExistingDirectories = new HashSet<String>();
80 private Set<String> fResult;
82 private FilteredTree fTree;
84 private TreeViewer fTreeViewer;
86 private Text dir;
88 private Button lookForNestedButton;
90 private Button searchButton;
92 private Button toggleSelectionButton;
94 private final ResourceManager fImageCache = new LocalResourceManager(
95 JFaceResources.getResources());
97 private final IEclipsePreferences prefs = new InstanceScope()
98 .getNode(Activator.getPluginId());
100 private final class ContentProvider implements ITreeContentProvider {
102 @SuppressWarnings("unchecked")
103 public Object[] getElements(Object inputElement) {
104 return ((Set<String>) inputElement).toArray();
107 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
108 // nothing
111 public void dispose() {
112 // nothing
115 public Object[] getChildren(Object parentElement) {
116 // nothing
117 return null;
120 public Object getParent(Object element) {
121 // nothing
122 return null;
125 public boolean hasChildren(Object element) {
126 // nothing
127 return false;
132 private final class RepositoryLabelProvider extends LabelProvider implements
133 IColorProvider {
135 @Override
136 public Image getImage(Object element) {
137 return fImageCache.createImage(UIIcons.REPOSITORY);
140 @Override
141 public String getText(Object element) {
142 return element.toString();
145 public Color getBackground(Object element) {
146 return null;
149 public Color getForeground(Object element) {
150 if (fExistingDirectories.contains(element))
151 return getShell().getDisplay().getSystemColor(SWT.COLOR_GRAY);
153 return null;
156 public void dispose() {
157 fImageCache.dispose();
163 * @param parentShell
164 * @param existingDirs
166 public RepositorySearchDialog(Shell parentShell,
167 Collection<String> existingDirs) {
168 super(parentShell);
169 this.fExistingDirectories.addAll(existingDirs);
170 setShellStyle(getShellStyle() | SWT.SHELL_TRIM);
171 setHelpAvailable(false);
176 * @return the directories
178 public Set<String> getDirectories() {
179 return fResult;
182 @Override
183 protected void configureShell(Shell newShell) {
184 super.configureShell(newShell);
185 newShell.setText(UIText.RepositorySearchDialog_AddGitRepositories);
186 setTitleImage(fImageCache.createImage(UIIcons.WIZBAN_IMPORT_REPO));
189 @Override
190 protected void okPressed() {
191 fResult = new HashSet<String>();
192 fResult.addAll(getCheckedItems());
193 super.okPressed();
196 @Override
197 protected Control createDialogArea(Composite parent) {
199 Composite titleParent = (Composite) super.createDialogArea(parent);
201 setTitle(UIText.RepositorySearchDialog_SearchTitle);
202 setMessage(UIText.RepositorySearchDialog_searchRepositoriesMessage);
204 Composite main = new Composite(titleParent, SWT.NONE);
205 main.setLayout(new GridLayout(1, false));
206 main.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
208 Group searchGroup = new Group(main, SWT.SHADOW_ETCHED_IN);
209 searchGroup.setText(UIText.RepositorySearchDialog_SearchCriteriaGroup);
210 searchGroup.setLayout(new GridLayout(3, false));
211 GridDataFactory.fillDefaults().grab(true, false).applyTo(searchGroup);
213 Label dirLabel = new Label(searchGroup, SWT.NONE);
214 dirLabel.setText(UIText.RepositorySearchDialog_directory);
215 dir = new Text(searchGroup, SWT.BORDER);
216 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true,
217 false).hint(300, SWT.DEFAULT).applyTo(dir);
218 dir.setToolTipText(UIText.RepositorySearchDialog_EnterDirectoryToolTip);
220 String initialPath = prefs.get(PREF_PATH, FS.DETECTED.userHome()
221 .toString());
223 dir.setText(initialPath);
225 Button browse = new Button(searchGroup, SWT.PUSH);
226 browse.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false,
227 1, 1));
228 browse.setText(UIText.RepositorySearchDialog_browse);
229 browse.addSelectionListener(new SelectionAdapter() {
231 @Override
232 public void widgetSelected(SelectionEvent e) {
233 DirectoryDialog dd = new DirectoryDialog(getShell());
234 dd.setFilterPath(dir.getText());
235 String directory = dd.open();
236 if (directory != null) {
237 setNeedsSearch();
238 dir.setText(directory);
239 prefs.put(PREF_PATH, directory);
240 try {
241 prefs.flush();
242 } catch (BackingStoreException e1) {
243 // ignore here
250 lookForNestedButton = new Button(searchGroup, SWT.CHECK);
251 lookForNestedButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER,
252 false, false, 3, 1));
253 lookForNestedButton.setSelection(prefs.getBoolean(PREF_DEEP_SEARCH,
254 false));
255 lookForNestedButton
256 .setText(UIText.RepositorySearchDialog_DeepSearch_button);
257 lookForNestedButton
258 .setToolTipText(UIText.RepositorySearchDialog_SearchRecursiveToolTip);
260 lookForNestedButton.addSelectionListener(new SelectionAdapter() {
262 @Override
263 public void widgetSelected(SelectionEvent e) {
264 prefs.putBoolean(PREF_DEEP_SEARCH, lookForNestedButton
265 .getSelection());
266 try {
267 prefs.flush();
268 } catch (BackingStoreException e1) {
269 // ignore
271 setNeedsSearch();
276 Group searchResultGroup = new Group(main, SWT.SHADOW_ETCHED_IN);
277 searchResultGroup
278 .setText(UIText.RepositorySearchDialog_SearchResultGroup);
279 searchResultGroup.setLayout(new GridLayout(2, false));
280 GridDataFactory.fillDefaults().applyTo(searchResultGroup);
282 // TODO for 3.4 compatibility, we must use this constructor
283 fTree = new FilteredTree(searchResultGroup, SWT.CHECK | SWT.BORDER,
284 new PatternFilter());
285 fTreeViewer = fTree.getViewer();
286 fTreeViewer
287 .addSelectionChangedListener(new ISelectionChangedListener() {
289 public void selectionChanged(SelectionChangedEvent event) {
290 // this is used to update the OK button when the
291 // keyboard
292 // is used to toggle the check boxes
293 enableOk();
297 GridDataFactory.fillDefaults().grab(true, true).minSize(0, 300)
298 .applyTo(fTree);
300 Composite buttonColumn = new Composite(searchResultGroup, SWT.NONE);
301 buttonColumn.setLayout(new GridLayout(1, false));
302 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).applyTo(
303 buttonColumn);
305 searchButton = new Button(buttonColumn, SWT.PUSH);
306 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(
307 searchButton);
308 searchButton.setText(UIText.RepositorySearchDialog_Search);
309 searchButton
310 .setToolTipText(UIText.RepositorySearchDialog_SearchTooltip);
311 searchButton.addSelectionListener(new SelectionAdapter() {
313 @Override
314 public void widgetSelected(SelectionEvent e) {
315 doSearch();
320 toggleSelectionButton = new Button(buttonColumn, SWT.NONE);
321 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(
322 toggleSelectionButton);
323 toggleSelectionButton
324 .setText(UIText.RepositorySearchDialog_ToggleSelectionButton);
325 toggleSelectionButton.setEnabled(false);
326 toggleSelectionButton.addSelectionListener(new SelectionAdapter() {
328 @Override
329 public void widgetSelected(SelectionEvent e) {
330 toggleSelection();
334 // TODO this isn't the most optimal way of handling this... ideally we
335 // should have some type of delay
336 // if we could use databinding an observeDelayedValue would totally work
337 // here
338 dir.addModifyListener(new ModifyListener() {
340 public void modifyText(ModifyEvent e) {
341 setNeedsSearch();
346 fTreeViewer.setContentProvider(new ContentProvider());
347 fTreeViewer.setLabelProvider(new RepositoryLabelProvider());
349 applyDialogFont(main);
351 return main;
354 @Override
355 protected void createButtonsForButtonBar(Composite parent) {
356 super.createButtonsForButtonBar(parent);
357 enableOk();
360 private void findGitDirsRecursive(File root, Set<String> strings,
361 IProgressMonitor monitor, boolean lookForNestedRepositories) {
363 if (!root.exists() || !root.isDirectory()) {
364 return;
366 File[] children = root.listFiles();
367 // simply ignore null
368 if (children == null)
369 return;
371 for (File child : children) {
372 if (monitor.isCanceled()) {
373 return;
376 if (child.isDirectory()
377 && RepositoryCache.FileKey.isGitRepository(child,
378 FS.DETECTED)) {
379 try {
380 strings.add(child.getCanonicalPath());
381 } catch (IOException e) {
382 // ignore here
384 monitor
385 .setTaskName(NLS
386 .bind(
387 UIText.RepositorySearchDialog_RepositoriesFound_message,
388 new Integer(strings.size())));
389 if (!lookForNestedRepositories)
390 return;
391 } else if (child.isDirectory()) {
392 monitor.subTask(child.getPath());
393 findGitDirsRecursive(child, strings, monitor,
394 lookForNestedRepositories);
400 private HashSet<String> getCheckedItems() {
401 HashSet<String> ret = new HashSet<String>();
402 for (TreeItem item : fTreeViewer.getTree().getItems())
403 if (item.getChecked())
404 ret.add((String) item.getData());
406 return ret;
409 private boolean hasCheckedItems() {
410 for (TreeItem item : fTreeViewer.getTree().getItems())
411 if (item.getChecked())
412 return true;
414 return false;
417 private void toggleSelection() {
418 for (TreeItem item : fTreeViewer.getTree().getItems())
419 item.setChecked(!item.getChecked());
420 enableOk();
423 private void doSearch() {
425 setMessage(null);
426 setErrorMessage(null);
427 // perform the search...
428 final Set<String> directories = new HashSet<String>();
429 final File file = new File(dir.getText());
430 final boolean lookForNested = lookForNestedButton.getSelection();
431 if (file.exists()) {
432 try {
433 prefs.put(PREF_PATH, file.getCanonicalPath());
434 try {
435 prefs.flush();
436 } catch (BackingStoreException e1) {
437 // ignore here
439 } catch (IOException e2) {
440 // ignore
443 IRunnableWithProgress action = new IRunnableWithProgress() {
445 public void run(IProgressMonitor monitor)
446 throws InvocationTargetException, InterruptedException {
448 try {
449 findGitDirsRecursive(file, directories, monitor,
450 lookForNested);
451 } catch (Exception ex) {
452 Activator.getDefault().getLog().log(
453 new Status(IStatus.ERROR, Activator
454 .getPluginId(), ex.getMessage(), ex));
456 if (monitor.isCanceled()) {
457 throw new InterruptedException();
461 try {
462 ProgressMonitorDialog pd = new ProgressMonitorDialog(getShell());
464 .getProgressMonitor()
465 .setTaskName(
466 UIText.RepositorySearchDialog_ScanningForRepositories_message);
467 pd.run(true, true, action);
469 } catch (InvocationTargetException e1) {
470 org.eclipse.egit.ui.Activator.handleError(
471 UIText.RepositorySearchDialog_errorOccurred, e1, true);
472 } catch (InterruptedException e1) {
473 // ignore
476 int foundOld = 0;
478 TreeSet<String> validDirs = new TreeSet<String>();
480 for (String foundDir : directories) {
481 if (!fExistingDirectories.contains(foundDir)) {
482 validDirs.add(foundDir);
483 } else {
484 foundOld++;
488 if (foundOld > 0) {
489 String message = NLS
490 .bind(
491 UIText.RepositorySearchDialog_SomeDirectoriesHiddenMessage,
492 Integer.valueOf(foundOld));
493 setMessage(message, IMessageProvider.INFORMATION);
494 } else if (directories.isEmpty())
495 setMessage(UIText.RepositorySearchDialog_NothingFoundMessage,
496 IMessageProvider.INFORMATION);
497 toggleSelectionButton.setEnabled(!validDirs.isEmpty());
498 fTreeViewer.setInput(validDirs);
499 // this sets all to selected
500 toggleSelection();
505 private void setNeedsSearch() {
506 fTreeViewer.setInput(null);
507 final File file = new File(dir.getText());
508 if (!file.exists()) {
509 setErrorMessage(NLS.bind(
510 UIText.RepositorySearchDialog_DirectoryNotFoundMessage, dir
511 .getText()));
512 } else {
513 setErrorMessage(null);
514 setMessage(UIText.RepositorySearchDialog_NoSearchAvailableMessage,
515 IMessageProvider.INFORMATION);
517 enableOk();
520 private void enableOk() {
521 boolean enable = hasCheckedItems();
522 getButton(OK).setEnabled(enable);
523 if (enable)
524 getButton(OK).setFocus();