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
9 * Mathias Kinzler (SAP AG) - initial implementation
10 *******************************************************************************/
11 package org
.eclipse
.egit
.ui
.internal
.repository
;
14 import java
.io
.IOException
;
15 import java
.lang
.reflect
.InvocationTargetException
;
16 import java
.util
.Collection
;
17 import java
.util
.HashSet
;
19 import java
.util
.TreeSet
;
21 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
22 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
23 import org
.eclipse
.core
.runtime
.IStatus
;
24 import org
.eclipse
.core
.runtime
.Status
;
25 import org
.eclipse
.core
.runtime
.preferences
.IEclipsePreferences
;
26 import org
.eclipse
.core
.runtime
.preferences
.InstanceScope
;
27 import org
.eclipse
.egit
.core
.Activator
;
28 import org
.eclipse
.egit
.ui
.UIIcons
;
29 import org
.eclipse
.egit
.ui
.UIText
;
30 import org
.eclipse
.jface
.dialogs
.IMessageProvider
;
31 import org
.eclipse
.jface
.dialogs
.ProgressMonitorDialog
;
32 import org
.eclipse
.jface
.dialogs
.TitleAreaDialog
;
33 import org
.eclipse
.jface
.layout
.GridDataFactory
;
34 import org
.eclipse
.jface
.operation
.IRunnableWithProgress
;
35 import org
.eclipse
.jface
.resource
.JFaceResources
;
36 import org
.eclipse
.jface
.resource
.LocalResourceManager
;
37 import org
.eclipse
.jface
.resource
.ResourceManager
;
38 import org
.eclipse
.jface
.viewers
.IColorProvider
;
39 import org
.eclipse
.jface
.viewers
.ISelectionChangedListener
;
40 import org
.eclipse
.jface
.viewers
.ITreeContentProvider
;
41 import org
.eclipse
.jface
.viewers
.LabelProvider
;
42 import org
.eclipse
.jface
.viewers
.SelectionChangedEvent
;
43 import org
.eclipse
.jface
.viewers
.TreeViewer
;
44 import org
.eclipse
.jface
.viewers
.Viewer
;
45 import org
.eclipse
.jgit
.lib
.RepositoryCache
;
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
.Display
;
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
;
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
;
88 private Button lookForNestedButton
;
90 private Button fSelectAllButton
;
92 private Button fDeselectAllButton
;
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
) {
111 public void dispose() {
115 public Object
[] getChildren(Object parentElement
) {
120 public Object
getParent(Object element
) {
125 public boolean hasChildren(Object element
) {
132 private final class RepositoryLabelProvider
extends LabelProvider
implements
136 public Image
getImage(Object element
) {
137 return fImageCache
.createImage(UIIcons
.REPOSITORY
);
141 public String
getText(Object element
) {
142 return element
.toString();
145 public Color
getBackground(Object element
) {
149 public Color
getForeground(Object element
) {
150 if (fExistingDirectories
.contains(element
))
151 return getShell().getDisplay().getSystemColor(SWT
.COLOR_GRAY
);
156 public void dispose() {
157 fImageCache
.dispose();
164 * @param existingDirs
166 public RepositorySearchDialog(Shell parentShell
,
167 Collection
<String
> existingDirs
) {
169 this.fExistingDirectories
.addAll(existingDirs
);
170 setShellStyle(getShellStyle() | SWT
.SHELL_TRIM
);
171 setHelpAvailable(false);
176 // we start the search asynchronously so that the dialog is already open
177 // when the progress dialog is showing
178 Display
.getDefault().asyncExec(new Runnable() {
188 * @return the directories
190 public Set
<String
> getDirectories() {
195 protected void configureShell(Shell newShell
) {
196 super.configureShell(newShell
);
197 newShell
.setText(UIText
.RepositorySearchDialog_searchRepositories
);
198 setTitleImage(fImageCache
.createImage(UIIcons
.WIZBAN_IMPORT_REPO
));
202 protected void okPressed() {
203 fResult
= new HashSet
<String
>();
204 fResult
.addAll(getCheckedItems());
209 protected Control
createDialogArea(Composite parent
) {
210 super.createDialogArea(parent
);
211 setTitle(UIText
.RepositorySearchDialog_searchRepositories
);
212 setMessage(UIText
.RepositorySearchDialog_searchRepositoriesMessage
);
214 Composite main
= new Composite(parent
, SWT
.NONE
);
215 main
.setLayout(new GridLayout(4, false));
216 main
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
218 Label dirLabel
= new Label(main
, SWT
.NONE
);
219 dirLabel
.setText(UIText
.RepositorySearchDialog_directory
);
220 dir
= new Text(main
, SWT
.BORDER
);
221 GridDataFactory
.fillDefaults().align(SWT
.FILL
, SWT
.CENTER
).grab(true,
222 false).span(2, 1).hint(300, SWT
.DEFAULT
).applyTo(dir
);
224 String initialPath
= prefs
.get(PREF_PATH
, ResourcesPlugin
225 .getWorkspace().getRoot().getLocation().toOSString());
227 dir
.setText(initialPath
);
229 Button browse
= new Button(main
, SWT
.PUSH
);
230 browse
.setLayoutData(new GridData(SWT
.FILL
, SWT
.CENTER
, false, false,
232 browse
.setText(UIText
.RepositorySearchDialog_browse
);
233 browse
.addSelectionListener(new SelectionAdapter() {
236 public void widgetSelected(SelectionEvent e
) {
237 DirectoryDialog dd
= new DirectoryDialog(getShell());
238 dd
.setFilterPath(dir
.getText());
239 String directory
= dd
.open();
240 if (directory
!= null) {
241 dir
.setText(directory
);
242 prefs
.put(PREF_PATH
, directory
);
245 } catch (BackingStoreException e1
) {
253 // TODO for 3.4 compatibility, we must use this constructor
254 fTree
= new FilteredTree(main
, SWT
.CHECK
| SWT
.BORDER
,
255 new PatternFilter());
256 fTreeViewer
= fTree
.getViewer();
258 .addSelectionChangedListener(new ISelectionChangedListener() {
260 public void selectionChanged(SelectionChangedEvent event
) {
261 // this is used to update the OK button when the
263 // is used to toggle the check boxes
264 getButton(OK
).setEnabled(hasCheckedItems());
268 GridDataFactory
.fillDefaults().grab(true, true).span(4, 1).minSize(0,
270 fTree
.setEnabled(false);
272 lookForNestedButton
= new Button(main
, SWT
.CHECK
);
273 lookForNestedButton
.setLayoutData(new GridData(SWT
.LEFT
, SWT
.CENTER
,
274 false, false, 2, 1));
275 lookForNestedButton
.setSelection(prefs
.getBoolean(PREF_DEEP_SEARCH
,
278 .setText(UIText
.RepositorySearchDialog_DeepSearch_button
);
280 lookForNestedButton
.addSelectionListener(new SelectionAdapter() {
283 public void widgetSelected(SelectionEvent e
) {
284 prefs
.putBoolean(PREF_DEEP_SEARCH
, lookForNestedButton
288 } catch (BackingStoreException e1
) {
296 fSelectAllButton
= new Button(main
, SWT
.NONE
);
297 fSelectAllButton
.setLayoutData(new GridData(SWT
.RIGHT
, SWT
.TOP
, false,
300 .setText(UIText
.RepositorySearchDialog_SelectAll_button
);
301 fSelectAllButton
.setEnabled(false);
302 fSelectAllButton
.addSelectionListener(new SelectionAdapter() {
305 public void widgetSelected(SelectionEvent e
) {
306 setAllSelected(true);
310 fDeselectAllButton
= new Button(main
, SWT
.NONE
);
311 fDeselectAllButton
.setLayoutData(new GridData(SWT
.RIGHT
, SWT
.TOP
,
312 false, false, 1, 1));
314 .setText(UIText
.RepositorySearchDialog_DeselectAll_button
);
315 fDeselectAllButton
.setEnabled(false);
316 fDeselectAllButton
.addSelectionListener(new SelectionAdapter() {
319 public void widgetSelected(SelectionEvent e
) {
320 setAllSelected(false);
324 // TODO this isn't the most optimal way of handling this... ideally we
325 // should have some type of delay
326 // if we could use databinding an observeDelayedValue would totally work
328 dir
.addModifyListener(new ModifyListener() {
330 public void modifyText(ModifyEvent e
) {
336 fTreeViewer
.setContentProvider(new ContentProvider());
337 fTreeViewer
.setLabelProvider(new RepositoryLabelProvider());
343 protected void createButtonsForButtonBar(Composite parent
) {
344 super.createButtonsForButtonBar(parent
);
345 getButton(OK
).setEnabled(false);
348 private void findGitDirsRecursive(File root
, Set
<String
> strings
,
349 IProgressMonitor monitor
, boolean lookForNestedRepositories
) {
351 if (!root
.exists() || !root
.isDirectory()) {
354 File
[] children
= root
.listFiles();
355 // simply ignore null
356 if (children
== null)
359 for (File child
: children
) {
360 if (monitor
.isCanceled()) {
364 if (child
.isDirectory()
365 && RepositoryCache
.FileKey
.isGitRepository(child
)) {
367 strings
.add(child
.getCanonicalPath());
368 } catch (IOException e
) {
374 UIText
.RepositorySearchDialog_RepositoriesFound_message
,
375 new Integer(strings
.size())));
376 if (!lookForNestedRepositories
)
378 } else if (child
.isDirectory()) {
379 monitor
.subTask(child
.getPath());
380 findGitDirsRecursive(child
, strings
, monitor
,
381 lookForNestedRepositories
);
387 private HashSet
<String
> getCheckedItems() {
388 HashSet
<String
> ret
= new HashSet
<String
>();
389 for (TreeItem item
: fTreeViewer
.getTree().getItems())
390 if (item
.getChecked())
391 ret
.add((String
) item
.getData());
396 private boolean hasCheckedItems() {
397 for (TreeItem item
: fTreeViewer
.getTree().getItems())
398 if (item
.getChecked())
404 private void setAllSelected(boolean selectionState
) {
405 for (TreeItem item
: fTreeViewer
.getTree().getItems())
406 item
.setChecked(selectionState
);
407 getButton(OK
).setEnabled(hasCheckedItems());
410 private void doSearch() {
412 // perform the search...
413 final Set
<String
> directories
= new HashSet
<String
>();
414 final File file
= new File(dir
.getText());
415 final boolean lookForNested
= lookForNestedButton
.getSelection();
418 prefs
.put(PREF_PATH
, file
.getCanonicalPath());
421 } catch (BackingStoreException e1
) {
424 } catch (IOException e2
) {
428 IRunnableWithProgress action
= new IRunnableWithProgress() {
430 public void run(IProgressMonitor monitor
)
431 throws InvocationTargetException
, InterruptedException
{
434 findGitDirsRecursive(file
, directories
, monitor
,
436 } catch (Exception ex
) {
437 Activator
.getDefault().getLog().log(
438 new Status(IStatus
.ERROR
, Activator
439 .getPluginId(), ex
.getMessage(), ex
));
444 ProgressMonitorDialog pd
= new ProgressMonitorDialog(getShell());
446 .getProgressMonitor()
448 UIText
.RepositorySearchDialog_ScanningForRepositories_message
);
449 pd
.run(true, true, action
);
451 } catch (InvocationTargetException e1
) {
452 org
.eclipse
.egit
.ui
.Activator
.handleError(
453 UIText
.RepositorySearchDialog_errorOccurred
, e1
, true);
454 } catch (InterruptedException e1
) {
458 boolean foundNew
= false;
461 TreeSet
<String
> validDirs
= new TreeSet
<String
>();
463 for (String foundDir
: directories
) {
464 if (!fExistingDirectories
.contains(foundDir
)) {
465 validDirs
.add(foundDir
);
475 UIText
.RepositorySearchDialog_SomeDirectoriesHiddenMessage
,
476 Integer
.valueOf(foundOld
));
477 setMessage(message
, IMessageProvider
.WARNING
);
480 fSelectAllButton
.setEnabled(foundNew
);
481 fDeselectAllButton
.setEnabled(foundNew
);
482 fTree
.setEnabled(validDirs
.size() > 0);
483 fTreeViewer
.setInput(validDirs
);