1 /*******************************************************************************
2 * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
4 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
7 * All rights reserved. This program and the accompanying materials
8 * are made available under the terms of the Eclipse Public License v1.0
9 * See LICENSE for the full license text, also available.
10 *******************************************************************************/
11 package org
.spearce
.egit
.ui
.internal
.components
;
14 import java
.net
.URISyntaxException
;
15 import java
.util
.Iterator
;
16 import java
.util
.LinkedList
;
17 import java
.util
.List
;
18 import java
.util
.regex
.Pattern
;
20 import org
.eclipse
.jface
.wizard
.WizardPage
;
21 import org
.eclipse
.osgi
.util
.NLS
;
22 import org
.eclipse
.swt
.SWT
;
23 import org
.eclipse
.swt
.events
.ModifyEvent
;
24 import org
.eclipse
.swt
.events
.ModifyListener
;
25 import org
.eclipse
.swt
.events
.SelectionAdapter
;
26 import org
.eclipse
.swt
.events
.SelectionEvent
;
27 import org
.eclipse
.swt
.events
.VerifyEvent
;
28 import org
.eclipse
.swt
.events
.VerifyListener
;
29 import org
.eclipse
.swt
.layout
.GridData
;
30 import org
.eclipse
.swt
.layout
.GridLayout
;
31 import org
.eclipse
.swt
.widgets
.Button
;
32 import org
.eclipse
.swt
.widgets
.Combo
;
33 import org
.eclipse
.swt
.widgets
.Composite
;
34 import org
.eclipse
.swt
.widgets
.Control
;
35 import org
.eclipse
.swt
.widgets
.Group
;
36 import org
.eclipse
.swt
.widgets
.Label
;
37 import org
.eclipse
.swt
.widgets
.Text
;
38 import org
.spearce
.egit
.ui
.Activator
;
39 import org
.spearce
.egit
.ui
.UIText
;
40 import org
.spearce
.jgit
.transport
.RemoteConfig
;
41 import org
.spearce
.jgit
.transport
.URIish
;
42 import org
.spearce
.jgit
.util
.FS
;
45 * Wizard page that allows the user entering the location of a remote repository
46 * by specifying URL manually or selecting a preconfigured remote repository.
48 public class RepositorySelectionPage
extends WizardPage
{
49 private static final int REMOTE_CONFIG_TEXT_MAX_LENGTH
= 80;
51 private static final String DEFAULT_REMOTE_NAME
= "origin";
53 private static final int S_GIT
= 0;
55 private static final int S_SSH
= 1;
57 private static final int S_SFTP
= 2;
59 private static final int S_HTTP
= 3;
61 private static final int S_HTTPS
= 4;
63 private static final int S_FTP
= 5;
65 private static final int S_FILE
= 6;
67 private static final String
[] DEFAULT_SCHEMES
;
69 DEFAULT_SCHEMES
= new String
[7];
70 DEFAULT_SCHEMES
[S_GIT
] = "git";
71 DEFAULT_SCHEMES
[S_SSH
] = "git+ssh";
72 DEFAULT_SCHEMES
[S_SFTP
] = "sftp";
73 DEFAULT_SCHEMES
[S_HTTP
] = "http";
74 DEFAULT_SCHEMES
[S_HTTPS
] = "https";
75 DEFAULT_SCHEMES
[S_FTP
] = "ftp";
76 DEFAULT_SCHEMES
[S_FILE
] = "file";
79 private static void setEnabledRecursively(final Control control
,
80 final boolean enable
) {
81 control
.setEnabled(enable
);
82 if (control
instanceof Composite
)
83 for (final Control child
: ((Composite
) control
).getChildren())
84 setEnabledRecursively(child
, enable
);
87 private final List
<RepositorySelectionListener
> selectionListeners
;
89 private final List
<RemoteConfig
> configuredRemotes
;
91 private Group authGroup
;
95 private Text hostText
;
97 private Text pathText
;
99 private Text userText
;
101 private Text passText
;
103 private Combo scheme
;
105 private Text portText
;
107 private int eventDepth
;
111 private RemoteConfig remoteConfig
;
113 private RemoteConfig exposedRemoteConfig
;
115 private URIish exposedURI
;
117 private Composite remotePanel
;
119 private Button remoteButton
;
121 private Combo remoteCombo
;
123 private Composite uriPanel
;
125 private Button uriButton
;
128 * Create repository selection page, allowing user specifying URI or
129 * (optionally) choosing from preconfigured remotes list.
131 * Wizard page is created without image, just with text description.
133 * @param sourceSelection
134 * true if dialog is used for source selection; false otherwise
135 * (destination selection). This indicates appropriate text
137 * @param configuredRemotes
138 * list of configured remotes that user may select as an
139 * alternative to manual URI specification. Remotes appear in
140 * given order in GUI, with {@value #DEFAULT_REMOTE_NAME} as the
141 * default choice. List may be null or empty - no remotes
142 * configurations appear in this case. Note that the provided
143 * list may be changed by this constructor.
145 public RepositorySelectionPage(final boolean sourceSelection
,
146 final List
<RemoteConfig
> configuredRemotes
) {
147 super(RepositorySelectionPage
.class.getName());
148 this.uri
= new URIish();
150 if (configuredRemotes
!= null)
151 removeUnusableRemoteConfigs(configuredRemotes
);
152 if (configuredRemotes
== null || configuredRemotes
.isEmpty())
153 this.configuredRemotes
= null;
155 this.configuredRemotes
= configuredRemotes
;
156 this.remoteConfig
= selectDefaultRemoteConfig();
159 if (sourceSelection
) {
160 setTitle(UIText
.RepositorySelectionPage_sourceSelectionTitle
);
161 setDescription(UIText
.RepositorySelectionPage_sourceSelectionDescription
);
163 setTitle(UIText
.RepositorySelectionPage_destinationSelectionTitle
);
164 setDescription(UIText
.RepositorySelectionPage_destinationSelectionDescription
);
166 selectionListeners
= new LinkedList
<RepositorySelectionListener
>();
170 * Create repository selection page, allowing user specifying URI, with no
171 * preconfigured remotes selection.
173 * @param sourceSelection
174 * true if dialog is used for source selection; false otherwise
175 * (destination selection). This indicates appropriate text
178 public RepositorySelectionPage(final boolean sourceSelection
) {
179 this(sourceSelection
, null);
183 * Add {@link RepositorySelectionListener} to list of listeners notified on
184 * repository selection change.
187 * listener that will be notified about changes
189 public void addRepositorySelectionListener(
190 final RepositorySelectionListener l
) {
191 selectionListeners
.add(l
);
194 public void createControl(final Composite parent
) {
195 final Composite panel
= new Composite(parent
, SWT
.NULL
);
196 panel
.setLayout(new GridLayout());
198 if (configuredRemotes
!= null)
199 createRemotePanel(panel
);
200 createUriPanel(panel
);
202 updateRemoteAndURIPanels();
207 private void createRemotePanel(final Composite parent
) {
208 remoteButton
= new Button(parent
, SWT
.RADIO
);
210 .setText(UIText
.RepositorySelectionPage_configuredRemoteChoice
212 remoteButton
.setSelection(true);
214 remotePanel
= new Composite(parent
, SWT
.NULL
);
215 remotePanel
.setLayout(new GridLayout());
216 final GridData gd
= new GridData();
217 gd
.grabExcessHorizontalSpace
= true;
218 gd
.horizontalAlignment
= SWT
.FILL
;
219 remotePanel
.setLayoutData(gd
);
221 remoteCombo
= new Combo(remotePanel
, SWT
.READ_ONLY
| SWT
.DROP_DOWN
);
222 final String items
[] = new String
[configuredRemotes
.size()];
224 for (final RemoteConfig rc
: configuredRemotes
)
225 items
[i
++] = getTextForRemoteConfig(rc
);
226 final int defaultIndex
= configuredRemotes
.indexOf(remoteConfig
);
227 remoteCombo
.setItems(items
);
228 remoteCombo
.select(defaultIndex
);
229 remoteCombo
.addSelectionListener(new SelectionAdapter() {
231 public void widgetSelected(SelectionEvent e
) {
232 final int idx
= remoteCombo
.getSelectionIndex();
233 remoteConfig
= configuredRemotes
.get(idx
);
239 private void createUriPanel(final Composite parent
) {
240 if (configuredRemotes
!= null) {
241 uriButton
= new Button(parent
, SWT
.RADIO
);
242 uriButton
.setText(UIText
.RepositorySelectionPage_uriChoice
+ ":");
243 uriButton
.addSelectionListener(new SelectionAdapter() {
244 public void widgetSelected(SelectionEvent e
) {
245 // occurs either on selection or unselection event
246 updateRemoteAndURIPanels();
252 uriPanel
= new Composite(parent
, SWT
.NULL
);
253 uriPanel
.setLayout(new GridLayout());
254 final GridData gd
= new GridData();
255 gd
.grabExcessHorizontalSpace
= true;
256 gd
.horizontalAlignment
= SWT
.FILL
;
257 uriPanel
.setLayoutData(gd
);
259 createLocationGroup(uriPanel
);
260 createConnectionGroup(uriPanel
);
261 authGroup
= createAuthenticationGroup(uriPanel
);
264 private void createLocationGroup(final Composite parent
) {
265 final Group g
= createGroup(parent
,
266 UIText
.RepositorySelectionPage_groupLocation
);
268 newLabel(g
, UIText
.RepositorySelectionPage_promptURI
+ ":");
269 uriText
= new Text(g
, SWT
.BORDER
);
270 uriText
.setLayoutData(createFieldGridData());
271 uriText
.addModifyListener(new ModifyListener() {
272 public void modifyText(final ModifyEvent e
) {
278 final URIish u
= new URIish(uriText
.getText());
279 safeSet(hostText
, u
.getHost());
280 safeSet(pathText
, u
.getPath());
281 safeSet(userText
, u
.getUser());
282 safeSet(passText
, u
.getPass());
285 portText
.setText(Integer
.toString(u
.getPort()));
287 portText
.setText("");
290 scheme
.select(S_FILE
);
292 scheme
.select(S_SSH
);
294 for (int i
= 0; i
< DEFAULT_SCHEMES
.length
; i
++) {
295 if (DEFAULT_SCHEMES
[i
].equals(u
.getScheme())) {
304 } catch (URISyntaxException err
) {
305 // leave uriText as it is, but clean up underlying uri and
308 hostText
.setText("");
309 pathText
.setText("");
310 userText
.setText("");
311 passText
.setText("");
312 portText
.setText("");
321 newLabel(g
, UIText
.RepositorySelectionPage_promptHost
+ ":");
322 hostText
= new Text(g
, SWT
.BORDER
);
323 hostText
.setLayoutData(createFieldGridData());
324 hostText
.addModifyListener(new ModifyListener() {
325 public void modifyText(final ModifyEvent e
) {
326 setURI(uri
.setHost(nullString(hostText
.getText())));
330 newLabel(g
, UIText
.RepositorySelectionPage_promptPath
+ ":");
331 pathText
= new Text(g
, SWT
.BORDER
);
332 pathText
.setLayoutData(createFieldGridData());
333 pathText
.addModifyListener(new ModifyListener() {
334 public void modifyText(final ModifyEvent e
) {
335 setURI(uri
.setPath(nullString(pathText
.getText())));
340 private Group
createAuthenticationGroup(final Composite parent
) {
341 final Group g
= createGroup(parent
,
342 UIText
.RepositorySelectionPage_groupAuthentication
);
344 newLabel(g
, UIText
.RepositorySelectionPage_promptUser
+ ":");
345 userText
= new Text(g
, SWT
.BORDER
);
346 userText
.setLayoutData(createFieldGridData());
347 userText
.addModifyListener(new ModifyListener() {
348 public void modifyText(final ModifyEvent e
) {
349 setURI(uri
.setUser(nullString(userText
.getText())));
353 newLabel(g
, UIText
.RepositorySelectionPage_promptPassword
+ ":");
354 passText
= new Text(g
, SWT
.BORDER
| SWT
.PASSWORD
);
355 passText
.setLayoutData(createFieldGridData());
359 private void createConnectionGroup(final Composite parent
) {
360 final Group g
= createGroup(parent
,
361 UIText
.RepositorySelectionPage_groupConnection
);
363 newLabel(g
, UIText
.RepositorySelectionPage_promptScheme
+ ":");
364 scheme
= new Combo(g
, SWT
.DROP_DOWN
| SWT
.READ_ONLY
);
365 scheme
.setItems(DEFAULT_SCHEMES
);
366 scheme
.addSelectionListener(new SelectionAdapter() {
367 public void widgetSelected(final SelectionEvent e
) {
368 final int idx
= scheme
.getSelectionIndex();
370 setURI(uri
.setScheme(null));
372 setURI(uri
.setScheme(nullString(scheme
.getItem(idx
))));
377 newLabel(g
, UIText
.RepositorySelectionPage_promptPort
+ ":");
378 portText
= new Text(g
, SWT
.BORDER
);
379 portText
.addVerifyListener(new VerifyListener() {
380 final Pattern p
= Pattern
.compile("^(?:[1-9][0-9]*)?$");
382 public void verifyText(final VerifyEvent e
) {
383 final String v
= portText
.getText();
385 v
.substring(0, e
.start
) + e
.text
+ v
.substring(e
.end
))
389 portText
.addModifyListener(new ModifyListener() {
390 public void modifyText(final ModifyEvent e
) {
391 final String val
= nullString(portText
.getText());
393 setURI(uri
.setPort(-1));
396 setURI(uri
.setPort(Integer
.parseInt(val
)));
397 } catch (NumberFormatException err
) {
398 // Ignore it for now.
405 private static Group
createGroup(final Composite parent
, final String text
) {
406 final Group g
= new Group(parent
, SWT
.NONE
);
407 final GridLayout layout
= new GridLayout();
408 layout
.numColumns
= 2;
411 final GridData gd
= new GridData();
412 gd
.grabExcessHorizontalSpace
= true;
413 gd
.horizontalAlignment
= SWT
.FILL
;
418 private static void newLabel(final Group g
, final String text
) {
419 new Label(g
, SWT
.NULL
).setText(text
);
422 private static GridData
createFieldGridData() {
423 return new GridData(SWT
.FILL
, SWT
.DEFAULT
, true, false);
427 * @return the URI entered in the Wizard page. null if URI is invalid or
428 * user chosen to select remote config instead of providing direct
431 public URIish
getURI() {
436 * @return the selected remote configuration in the Wizard page. null if
437 * user chosen to select repository as URI.
439 public RemoteConfig
getRemoteConfig() {
440 return exposedRemoteConfig
;
443 private static boolean isGIT(final URIish uri
) {
444 return "git".equals(uri
.getScheme());
447 private static boolean isFile(final URIish uri
) {
448 if ("file".equals(uri
.getScheme()))
450 if (uri
.getHost() != null || uri
.getPort() > 0 || uri
.getUser() != null
451 || uri
.getPass() != null || uri
.getPath() == null)
453 if (uri
.getScheme() == null)
454 return FS
.resolve(new File("."), uri
.getPath()).isDirectory();
458 private static boolean isSSH(final URIish uri
) {
461 final String scheme
= uri
.getScheme();
462 if ("ssh".equals(scheme
))
464 if ("ssh+git".equals(scheme
))
466 if ("git+ssh".equals(scheme
))
468 if (scheme
== null && uri
.getHost() != null && uri
.getPath() != null)
473 private static String
nullString(final String value
) {
476 final String v
= value
.trim();
477 return v
.length() == 0 ?
null : v
;
480 private static void safeSet(final Text text
, final String value
) {
481 text
.setText(value
!= null ? value
: "");
484 private boolean isURISelected() {
485 return configuredRemotes
== null || uriButton
.getSelection();
488 private void setURI(final URIish u
) {
491 if (eventDepth
== 1) {
493 uriText
.setText(uri
.toString());
501 private static void removeUnusableRemoteConfigs(
502 final List
<RemoteConfig
> remotes
) {
503 final Iterator
<RemoteConfig
> iter
= remotes
.iterator();
504 while (iter
.hasNext()) {
505 final RemoteConfig rc
= iter
.next();
506 if (rc
.getURIs().isEmpty())
511 private RemoteConfig
selectDefaultRemoteConfig() {
512 for (final RemoteConfig rc
: configuredRemotes
)
513 if (getTextForRemoteConfig(rc
) == DEFAULT_REMOTE_NAME
)
515 return configuredRemotes
.get(0);
518 private static String
getTextForRemoteConfig(final RemoteConfig rc
) {
519 final StringBuilder sb
= new StringBuilder(rc
.getName());
521 boolean first
= true;
522 for (final URIish u
: rc
.getURIs()) {
523 final String uString
= u
.toString();
528 if (sb
.length() + uString
.length() > REMOTE_CONFIG_TEXT_MAX_LENGTH
) {
535 return sb
.toString();
538 private void checkPage() {
539 if (isURISelected()) {
541 if (uriText
.getText().length() == 0) {
542 selectionIncomplete(null);
547 final URIish finalURI
= new URIish(uriText
.getText());
548 String proto
= finalURI
.getScheme();
549 if (proto
== null && scheme
.getSelectionIndex() >= 0)
550 proto
= scheme
.getItem(scheme
.getSelectionIndex());
552 if (uri
.getPath() == null) {
553 selectionIncomplete(NLS
.bind(
554 UIText
.RepositorySelectionPage_fieldRequired
,
555 UIText
.RepositorySelectionPage_promptPath
, proto
));
559 if (isFile(finalURI
)) {
560 String badField
= null;
561 if (uri
.getHost() != null)
562 badField
= UIText
.RepositorySelectionPage_promptHost
;
563 else if (uri
.getUser() != null)
564 badField
= UIText
.RepositorySelectionPage_promptUser
;
565 else if (uri
.getPass() != null)
566 badField
= UIText
.RepositorySelectionPage_promptPassword
;
567 if (badField
!= null) {
568 selectionIncomplete(NLS
570 UIText
.RepositorySelectionPage_fieldNotSupported
,
575 final File d
= FS
.resolve(new File("."), uri
.getPath());
577 selectionIncomplete(NLS
.bind(
578 UIText
.RepositorySelectionPage_fileNotFound
, d
579 .getAbsolutePath()));
583 selectionComplete(finalURI
, null);
587 if (uri
.getHost() == null) {
588 selectionIncomplete(NLS
.bind(
589 UIText
.RepositorySelectionPage_fieldRequired
,
590 UIText
.RepositorySelectionPage_promptHost
, proto
));
594 if (isGIT(finalURI
)) {
595 String badField
= null;
596 if (uri
.getUser() != null)
597 badField
= UIText
.RepositorySelectionPage_promptUser
;
598 else if (uri
.getPass() != null)
599 badField
= UIText
.RepositorySelectionPage_promptPassword
;
600 if (badField
!= null) {
601 selectionIncomplete(NLS
603 UIText
.RepositorySelectionPage_fieldNotSupported
,
609 selectionComplete(finalURI
, null);
611 } catch (URISyntaxException e
) {
612 selectionIncomplete(e
.getReason());
614 } catch (Exception e
) {
615 Activator
.logError("Error validating " + getClass().getName(),
617 selectionIncomplete(UIText
.RepositorySelectionPage_internalError
);
621 assert remoteButton
.getSelection();
622 selectionComplete(null, remoteConfig
);
627 private void selectionIncomplete(final String errorMessage
) {
628 setExposedSelection(null, null);
629 setErrorMessage(errorMessage
);
630 setPageComplete(false);
633 private void selectionComplete(final URIish u
, final RemoteConfig rc
) {
634 setExposedSelection(u
, rc
);
635 setErrorMessage(null);
636 setPageComplete(true);
639 private void setExposedSelection(final URIish u
, final RemoteConfig rc
) {
640 if (u
== exposedURI
&& rc
== exposedRemoteConfig
) // nothing changed
643 this.exposedRemoteConfig
= rc
;
645 for (final RepositorySelectionListener l
: selectionListeners
)
646 l
.selectionChanged(u
, rc
);
649 private void updateRemoteAndURIPanels() {
650 setEnabledRecursively(uriPanel
, isURISelected());
651 if (uriPanel
.getEnabled())
653 if (configuredRemotes
!= null)
654 setEnabledRecursively(remotePanel
, !isURISelected());
657 private void updateAuthGroup() {
658 switch (scheme
.getSelectionIndex()) {
660 hostText
.setEnabled(true);
661 portText
.setEnabled(true);
662 setEnabledRecursively(authGroup
, false);
669 hostText
.setEnabled(true);
670 portText
.setEnabled(true);
671 setEnabledRecursively(authGroup
, true);
674 hostText
.setEnabled(false);
675 portText
.setEnabled(false);
676 setEnabledRecursively(authGroup
, false);