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
.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
;
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 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
) {
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 * @return the directories
178 public Set
<String
> getDirectories() {
183 protected void configureShell(Shell newShell
) {
184 super.configureShell(newShell
);
185 newShell
.setText(UIText
.RepositorySearchDialog_AddGitRepositories
);
186 setTitleImage(fImageCache
.createImage(UIIcons
.WIZBAN_IMPORT_REPO
));
190 protected void okPressed() {
191 fResult
= new HashSet
<String
>();
192 fResult
.addAll(getCheckedItems());
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()
223 dir
.setText(initialPath
);
225 Button browse
= new Button(searchGroup
, SWT
.PUSH
);
226 browse
.setLayoutData(new GridData(SWT
.FILL
, SWT
.CENTER
, false, false,
228 browse
.setText(UIText
.RepositorySearchDialog_browse
);
229 browse
.addSelectionListener(new SelectionAdapter() {
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) {
238 dir
.setText(directory
);
239 prefs
.put(PREF_PATH
, directory
);
242 } catch (BackingStoreException e1
) {
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
,
256 .setText(UIText
.RepositorySearchDialog_DeepSearch_button
);
258 .setToolTipText(UIText
.RepositorySearchDialog_SearchRecursiveToolTip
);
260 lookForNestedButton
.addSelectionListener(new SelectionAdapter() {
263 public void widgetSelected(SelectionEvent e
) {
264 prefs
.putBoolean(PREF_DEEP_SEARCH
, lookForNestedButton
268 } catch (BackingStoreException e1
) {
276 Group searchResultGroup
= new Group(main
, SWT
.SHADOW_ETCHED_IN
);
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();
287 .addSelectionChangedListener(new ISelectionChangedListener() {
289 public void selectionChanged(SelectionChangedEvent event
) {
290 // this is used to update the OK button when the
292 // is used to toggle the check boxes
297 GridDataFactory
.fillDefaults().grab(true, true).minSize(0, 300)
300 Composite buttonColumn
= new Composite(searchResultGroup
, SWT
.NONE
);
301 buttonColumn
.setLayout(new GridLayout(1, false));
302 GridDataFactory
.fillDefaults().align(SWT
.FILL
, SWT
.BEGINNING
).applyTo(
305 searchButton
= new Button(buttonColumn
, SWT
.PUSH
);
306 GridDataFactory
.fillDefaults().align(SWT
.FILL
, SWT
.FILL
).applyTo(
308 searchButton
.setText(UIText
.RepositorySearchDialog_Search
);
310 .setToolTipText(UIText
.RepositorySearchDialog_SearchTooltip
);
311 searchButton
.addSelectionListener(new SelectionAdapter() {
314 public void widgetSelected(SelectionEvent e
) {
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() {
329 public void widgetSelected(SelectionEvent e
) {
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
338 dir
.addModifyListener(new ModifyListener() {
340 public void modifyText(ModifyEvent e
) {
346 fTreeViewer
.setContentProvider(new ContentProvider());
347 fTreeViewer
.setLabelProvider(new RepositoryLabelProvider());
349 applyDialogFont(main
);
355 protected void createButtonsForButtonBar(Composite parent
) {
356 super.createButtonsForButtonBar(parent
);
360 private void findGitDirsRecursive(File root
, Set
<String
> strings
,
361 IProgressMonitor monitor
, boolean lookForNestedRepositories
) {
363 if (!root
.exists() || !root
.isDirectory()) {
366 File
[] children
= root
.listFiles();
367 // simply ignore null
368 if (children
== null)
371 for (File child
: children
) {
372 if (monitor
.isCanceled()) {
376 if (child
.isDirectory()
377 && RepositoryCache
.FileKey
.isGitRepository(child
,
380 strings
.add(child
.getCanonicalPath());
381 } catch (IOException e
) {
387 UIText
.RepositorySearchDialog_RepositoriesFound_message
,
388 new Integer(strings
.size())));
389 if (!lookForNestedRepositories
)
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());
409 private boolean hasCheckedItems() {
410 for (TreeItem item
: fTreeViewer
.getTree().getItems())
411 if (item
.getChecked())
417 private void toggleSelection() {
418 for (TreeItem item
: fTreeViewer
.getTree().getItems())
419 item
.setChecked(!item
.getChecked());
423 private void doSearch() {
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();
433 prefs
.put(PREF_PATH
, file
.getCanonicalPath());
436 } catch (BackingStoreException e1
) {
439 } catch (IOException e2
) {
443 IRunnableWithProgress action
= new IRunnableWithProgress() {
445 public void run(IProgressMonitor monitor
)
446 throws InvocationTargetException
, InterruptedException
{
449 findGitDirsRecursive(file
, directories
, monitor
,
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();
462 ProgressMonitorDialog pd
= new ProgressMonitorDialog(getShell());
464 .getProgressMonitor()
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
) {
478 TreeSet
<String
> validDirs
= new TreeSet
<String
>();
480 for (String foundDir
: directories
) {
481 if (!fExistingDirectories
.contains(foundDir
)) {
482 validDirs
.add(foundDir
);
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
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
513 setErrorMessage(null);
514 setMessage(UIText
.RepositorySearchDialog_NoSearchAvailableMessage
,
515 IMessageProvider
.INFORMATION
);
520 private void enableOk() {
521 boolean enable
= hasCheckedItems();
522 getButton(OK
).setEnabled(enable
);
524 getButton(OK
).setFocus();