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
.jgit
.util
.FS
;
47 import org
.eclipse
.osgi
.util
.NLS
;
48 import org
.eclipse
.swt
.SWT
;
49 import org
.eclipse
.swt
.events
.ModifyEvent
;
50 import org
.eclipse
.swt
.events
.ModifyListener
;
51 import org
.eclipse
.swt
.events
.SelectionAdapter
;
52 import org
.eclipse
.swt
.events
.SelectionEvent
;
53 import org
.eclipse
.swt
.graphics
.Color
;
54 import org
.eclipse
.swt
.graphics
.Image
;
55 import org
.eclipse
.swt
.layout
.GridData
;
56 import org
.eclipse
.swt
.layout
.GridLayout
;
57 import org
.eclipse
.swt
.widgets
.Button
;
58 import org
.eclipse
.swt
.widgets
.Composite
;
59 import org
.eclipse
.swt
.widgets
.Control
;
60 import org
.eclipse
.swt
.widgets
.DirectoryDialog
;
61 import org
.eclipse
.swt
.widgets
.Display
;
62 import org
.eclipse
.swt
.widgets
.Label
;
63 import org
.eclipse
.swt
.widgets
.Shell
;
64 import org
.eclipse
.swt
.widgets
.Text
;
65 import org
.eclipse
.swt
.widgets
.TreeItem
;
66 import org
.eclipse
.ui
.dialogs
.FilteredTree
;
67 import org
.eclipse
.ui
.dialogs
.PatternFilter
;
68 import org
.osgi
.service
.prefs
.BackingStoreException
;
71 * Searches for Git directories under a path that can be selected by the user
73 public class RepositorySearchDialog
extends TitleAreaDialog
{
75 private static final String PREF_DEEP_SEARCH
= "RepositorySearchDialogDeepSearch"; //$NON-NLS-1$
77 private static final String PREF_PATH
= "RepositorySearchDialogSearchPath"; //$NON-NLS-1$
79 private final Set
<String
> fExistingDirectories
= new HashSet
<String
>();
81 private Set
<String
> fResult
;
83 private FilteredTree fTree
;
85 private TreeViewer fTreeViewer
;
89 private Button lookForNestedButton
;
91 private Button fSelectAllButton
;
93 private Button fDeselectAllButton
;
95 private final ResourceManager fImageCache
= new LocalResourceManager(
96 JFaceResources
.getResources());
98 private final IEclipsePreferences prefs
= new InstanceScope()
99 .getNode(Activator
.getPluginId());
101 private final class ContentProvider
implements ITreeContentProvider
{
103 @SuppressWarnings("unchecked")
104 public Object
[] getElements(Object inputElement
) {
105 return ((Set
<String
>) inputElement
).toArray();
108 public void inputChanged(Viewer viewer
, Object oldInput
, Object newInput
) {
112 public void dispose() {
116 public Object
[] getChildren(Object parentElement
) {
121 public Object
getParent(Object element
) {
126 public boolean hasChildren(Object element
) {
133 private final class RepositoryLabelProvider
extends LabelProvider
implements
137 public Image
getImage(Object element
) {
138 return fImageCache
.createImage(UIIcons
.REPOSITORY
);
142 public String
getText(Object element
) {
143 return element
.toString();
146 public Color
getBackground(Object element
) {
150 public Color
getForeground(Object element
) {
151 if (fExistingDirectories
.contains(element
))
152 return getShell().getDisplay().getSystemColor(SWT
.COLOR_GRAY
);
157 public void dispose() {
158 fImageCache
.dispose();
165 * @param existingDirs
167 public RepositorySearchDialog(Shell parentShell
,
168 Collection
<String
> existingDirs
) {
170 this.fExistingDirectories
.addAll(existingDirs
);
171 setShellStyle(getShellStyle() | SWT
.SHELL_TRIM
);
172 setHelpAvailable(false);
177 // we start the search asynchronously so that the dialog is already open
178 // when the progress dialog is showing
179 Display
.getDefault().asyncExec(new Runnable() {
189 * @return the directories
191 public Set
<String
> getDirectories() {
196 protected void configureShell(Shell newShell
) {
197 super.configureShell(newShell
);
198 newShell
.setText(UIText
.RepositorySearchDialog_searchRepositories
);
199 setTitleImage(fImageCache
.createImage(UIIcons
.WIZBAN_IMPORT_REPO
));
203 protected void okPressed() {
204 fResult
= new HashSet
<String
>();
205 fResult
.addAll(getCheckedItems());
210 protected Control
createDialogArea(Composite parent
) {
211 super.createDialogArea(parent
);
212 setTitle(UIText
.RepositorySearchDialog_searchRepositories
);
213 setMessage(UIText
.RepositorySearchDialog_searchRepositoriesMessage
);
215 Composite main
= new Composite(parent
, SWT
.NONE
);
216 main
.setLayout(new GridLayout(4, false));
217 main
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
219 Label dirLabel
= new Label(main
, SWT
.NONE
);
220 dirLabel
.setText(UIText
.RepositorySearchDialog_directory
);
221 dir
= new Text(main
, SWT
.BORDER
);
222 GridDataFactory
.fillDefaults().align(SWT
.FILL
, SWT
.CENTER
).grab(true,
223 false).span(2, 1).hint(300, SWT
.DEFAULT
).applyTo(dir
);
225 String initialPath
= prefs
.get(PREF_PATH
, ResourcesPlugin
226 .getWorkspace().getRoot().getLocation().toOSString());
228 dir
.setText(initialPath
);
230 Button browse
= new Button(main
, SWT
.PUSH
);
231 browse
.setLayoutData(new GridData(SWT
.FILL
, SWT
.CENTER
, false, false,
233 browse
.setText(UIText
.RepositorySearchDialog_browse
);
234 browse
.addSelectionListener(new SelectionAdapter() {
237 public void widgetSelected(SelectionEvent e
) {
238 DirectoryDialog dd
= new DirectoryDialog(getShell());
239 dd
.setFilterPath(dir
.getText());
240 String directory
= dd
.open();
241 if (directory
!= null) {
242 dir
.setText(directory
);
243 prefs
.put(PREF_PATH
, directory
);
246 } catch (BackingStoreException e1
) {
254 // TODO for 3.4 compatibility, we must use this constructor
255 fTree
= new FilteredTree(main
, SWT
.CHECK
| SWT
.BORDER
,
256 new PatternFilter());
257 fTreeViewer
= fTree
.getViewer();
259 .addSelectionChangedListener(new ISelectionChangedListener() {
261 public void selectionChanged(SelectionChangedEvent event
) {
262 // this is used to update the OK button when the
264 // is used to toggle the check boxes
265 getButton(OK
).setEnabled(hasCheckedItems());
269 GridDataFactory
.fillDefaults().grab(true, true).span(4, 1).minSize(0,
271 fTree
.setEnabled(false);
273 lookForNestedButton
= new Button(main
, SWT
.CHECK
);
274 lookForNestedButton
.setLayoutData(new GridData(SWT
.LEFT
, SWT
.CENTER
,
275 false, false, 2, 1));
276 lookForNestedButton
.setSelection(prefs
.getBoolean(PREF_DEEP_SEARCH
,
279 .setText(UIText
.RepositorySearchDialog_DeepSearch_button
);
281 lookForNestedButton
.addSelectionListener(new SelectionAdapter() {
284 public void widgetSelected(SelectionEvent e
) {
285 prefs
.putBoolean(PREF_DEEP_SEARCH
, lookForNestedButton
289 } catch (BackingStoreException e1
) {
297 fSelectAllButton
= new Button(main
, SWT
.NONE
);
298 fSelectAllButton
.setLayoutData(new GridData(SWT
.RIGHT
, SWT
.TOP
, false,
301 .setText(UIText
.RepositorySearchDialog_SelectAll_button
);
302 fSelectAllButton
.setEnabled(false);
303 fSelectAllButton
.addSelectionListener(new SelectionAdapter() {
306 public void widgetSelected(SelectionEvent e
) {
307 setAllSelected(true);
311 fDeselectAllButton
= new Button(main
, SWT
.NONE
);
312 fDeselectAllButton
.setLayoutData(new GridData(SWT
.RIGHT
, SWT
.TOP
,
313 false, false, 1, 1));
315 .setText(UIText
.RepositorySearchDialog_DeselectAll_button
);
316 fDeselectAllButton
.setEnabled(false);
317 fDeselectAllButton
.addSelectionListener(new SelectionAdapter() {
320 public void widgetSelected(SelectionEvent e
) {
321 setAllSelected(false);
325 // TODO this isn't the most optimal way of handling this... ideally we
326 // should have some type of delay
327 // if we could use databinding an observeDelayedValue would totally work
329 dir
.addModifyListener(new ModifyListener() {
331 public void modifyText(ModifyEvent e
) {
337 fTreeViewer
.setContentProvider(new ContentProvider());
338 fTreeViewer
.setLabelProvider(new RepositoryLabelProvider());
344 protected void createButtonsForButtonBar(Composite parent
) {
345 super.createButtonsForButtonBar(parent
);
346 getButton(OK
).setEnabled(false);
349 private void findGitDirsRecursive(File root
, Set
<String
> strings
,
350 IProgressMonitor monitor
, boolean lookForNestedRepositories
) {
352 if (!root
.exists() || !root
.isDirectory()) {
355 File
[] children
= root
.listFiles();
356 // simply ignore null
357 if (children
== null)
360 for (File child
: children
) {
361 if (monitor
.isCanceled()) {
365 if (child
.isDirectory()
366 && RepositoryCache
.FileKey
.isGitRepository(child
, FS
.DETECTED
)) {
368 strings
.add(child
.getCanonicalPath());
369 } catch (IOException e
) {
375 UIText
.RepositorySearchDialog_RepositoriesFound_message
,
376 new Integer(strings
.size())));
377 if (!lookForNestedRepositories
)
379 } else if (child
.isDirectory()) {
380 monitor
.subTask(child
.getPath());
381 findGitDirsRecursive(child
, strings
, monitor
,
382 lookForNestedRepositories
);
388 private HashSet
<String
> getCheckedItems() {
389 HashSet
<String
> ret
= new HashSet
<String
>();
390 for (TreeItem item
: fTreeViewer
.getTree().getItems())
391 if (item
.getChecked())
392 ret
.add((String
) item
.getData());
397 private boolean hasCheckedItems() {
398 for (TreeItem item
: fTreeViewer
.getTree().getItems())
399 if (item
.getChecked())
405 private void setAllSelected(boolean selectionState
) {
406 for (TreeItem item
: fTreeViewer
.getTree().getItems())
407 item
.setChecked(selectionState
);
408 getButton(OK
).setEnabled(hasCheckedItems());
411 private void doSearch() {
413 // perform the search...
414 final Set
<String
> directories
= new HashSet
<String
>();
415 final File file
= new File(dir
.getText());
416 final boolean lookForNested
= lookForNestedButton
.getSelection();
419 prefs
.put(PREF_PATH
, file
.getCanonicalPath());
422 } catch (BackingStoreException e1
) {
425 } catch (IOException e2
) {
429 IRunnableWithProgress action
= new IRunnableWithProgress() {
431 public void run(IProgressMonitor monitor
)
432 throws InvocationTargetException
, InterruptedException
{
435 findGitDirsRecursive(file
, directories
, monitor
,
437 } catch (Exception ex
) {
438 Activator
.getDefault().getLog().log(
439 new Status(IStatus
.ERROR
, Activator
440 .getPluginId(), ex
.getMessage(), ex
));
445 ProgressMonitorDialog pd
= new ProgressMonitorDialog(getShell());
447 .getProgressMonitor()
449 UIText
.RepositorySearchDialog_ScanningForRepositories_message
);
450 pd
.run(true, true, action
);
452 } catch (InvocationTargetException e1
) {
453 org
.eclipse
.egit
.ui
.Activator
.handleError(
454 UIText
.RepositorySearchDialog_errorOccurred
, e1
, true);
455 } catch (InterruptedException e1
) {
459 boolean foundNew
= false;
462 TreeSet
<String
> validDirs
= new TreeSet
<String
>();
464 for (String foundDir
: directories
) {
465 if (!fExistingDirectories
.contains(foundDir
)) {
466 validDirs
.add(foundDir
);
476 UIText
.RepositorySearchDialog_SomeDirectoriesHiddenMessage
,
477 Integer
.valueOf(foundOld
));
478 setMessage(message
, IMessageProvider
.INFORMATION
);
481 fSelectAllButton
.setEnabled(foundNew
);
482 fDeselectAllButton
.setEnabled(foundNew
);
483 fTree
.setEnabled(validDirs
.size() > 0);
484 fTreeViewer
.setInput(validDirs
);