1 /*******************************************************************************
2 * Copyright (C) 2007, 2008, 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
.List
;
17 import java
.util
.regex
.Pattern
;
19 import org
.eclipse
.osgi
.util
.NLS
;
20 import org
.eclipse
.swt
.SWT
;
21 import org
.eclipse
.swt
.events
.ModifyEvent
;
22 import org
.eclipse
.swt
.events
.ModifyListener
;
23 import org
.eclipse
.swt
.events
.SelectionAdapter
;
24 import org
.eclipse
.swt
.events
.SelectionEvent
;
25 import org
.eclipse
.swt
.events
.VerifyEvent
;
26 import org
.eclipse
.swt
.events
.VerifyListener
;
27 import org
.eclipse
.swt
.layout
.GridData
;
28 import org
.eclipse
.swt
.layout
.GridLayout
;
29 import org
.eclipse
.swt
.widgets
.Button
;
30 import org
.eclipse
.swt
.widgets
.Combo
;
31 import org
.eclipse
.swt
.widgets
.Composite
;
32 import org
.eclipse
.swt
.widgets
.Control
;
33 import org
.eclipse
.swt
.widgets
.Group
;
34 import org
.eclipse
.swt
.widgets
.Label
;
35 import org
.eclipse
.swt
.widgets
.Text
;
36 import org
.spearce
.egit
.ui
.Activator
;
37 import org
.spearce
.egit
.ui
.UIText
;
38 import org
.spearce
.jgit
.transport
.RemoteConfig
;
39 import org
.spearce
.jgit
.transport
.URIish
;
40 import org
.spearce
.jgit
.util
.FS
;
43 * Wizard page that allows the user entering the location of a remote repository
44 * by specifying URL manually or selecting a preconfigured remote repository.
46 public class RepositorySelectionPage
extends BaseWizardPage
{
47 private static final int REMOTE_CONFIG_TEXT_MAX_LENGTH
= 80;
49 private static final String DEFAULT_REMOTE_NAME
= "origin";
51 private static final int S_GIT
= 0;
53 private static final int S_SSH
= 1;
55 private static final int S_SFTP
= 2;
57 private static final int S_HTTP
= 3;
59 private static final int S_HTTPS
= 4;
61 private static final int S_FTP
= 5;
63 private static final int S_FILE
= 6;
65 private static final String
[] DEFAULT_SCHEMES
;
67 DEFAULT_SCHEMES
= new String
[7];
68 DEFAULT_SCHEMES
[S_GIT
] = "git";
69 DEFAULT_SCHEMES
[S_SSH
] = "git+ssh";
70 DEFAULT_SCHEMES
[S_SFTP
] = "sftp";
71 DEFAULT_SCHEMES
[S_HTTP
] = "http";
72 DEFAULT_SCHEMES
[S_HTTPS
] = "https";
73 DEFAULT_SCHEMES
[S_FTP
] = "ftp";
74 DEFAULT_SCHEMES
[S_FILE
] = "file";
77 private static void setEnabledRecursively(final Control control
,
78 final boolean enable
) {
79 control
.setEnabled(enable
);
80 if (control
instanceof Composite
)
81 for (final Control child
: ((Composite
) control
).getChildren())
82 setEnabledRecursively(child
, enable
);
85 private final List
<RemoteConfig
> configuredRemotes
;
87 private Group authGroup
;
91 private Text hostText
;
93 private Text pathText
;
95 private Text userText
;
97 private Text passText
;
101 private Text portText
;
103 private int eventDepth
;
107 private RemoteConfig remoteConfig
;
109 private RepositorySelection selection
;
111 private Composite remotePanel
;
113 private Button remoteButton
;
115 private Combo remoteCombo
;
117 private Composite uriPanel
;
119 private Button uriButton
;
122 * Create repository selection page, allowing user specifying URI or
123 * (optionally) choosing from preconfigured remotes list.
125 * Wizard page is created without image, just with text description.
127 * @param sourceSelection
128 * true if dialog is used for source selection; false otherwise
129 * (destination selection). This indicates appropriate text
131 * @param configuredRemotes
132 * list of configured remotes that user may select as an
133 * alternative to manual URI specification. Remotes appear in
134 * given order in GUI, with {@value #DEFAULT_REMOTE_NAME} as the
135 * default choice. List may be null or empty - no remotes
136 * configurations appear in this case. Note that the provided
137 * list may be changed by this constructor.
139 public RepositorySelectionPage(final boolean sourceSelection
,
140 final List
<RemoteConfig
> configuredRemotes
) {
141 super(RepositorySelectionPage
.class.getName());
142 this.uri
= new URIish();
144 if (configuredRemotes
!= null)
145 removeUnusableRemoteConfigs(configuredRemotes
);
146 if (configuredRemotes
== null || configuredRemotes
.isEmpty())
147 this.configuredRemotes
= null;
149 this.configuredRemotes
= configuredRemotes
;
150 this.remoteConfig
= selectDefaultRemoteConfig();
152 selection
= RepositorySelection
.INVALID_SELECTION
;
154 if (sourceSelection
) {
155 setTitle(UIText
.RepositorySelectionPage_sourceSelectionTitle
);
156 setDescription(UIText
.RepositorySelectionPage_sourceSelectionDescription
);
158 setTitle(UIText
.RepositorySelectionPage_destinationSelectionTitle
);
159 setDescription(UIText
.RepositorySelectionPage_destinationSelectionDescription
);
164 * Create repository selection page, allowing user specifying URI, with no
165 * preconfigured remotes selection.
167 * @param sourceSelection
168 * true if dialog is used for source selection; false otherwise
169 * (destination selection). This indicates appropriate text
172 public RepositorySelectionPage(final boolean sourceSelection
) {
173 this(sourceSelection
, null);
177 * @return repository selection representing current page state.
179 public RepositorySelection
getSelection() {
184 * Compare current repository selection set by user to provided one.
187 * repository selection to compare.
188 * @return true if provided selection is equal to current page selection,
191 public boolean selectionEquals(final RepositorySelection s
) {
192 return selection
.equals(s
);
195 public void createControl(final Composite parent
) {
196 final Composite panel
= new Composite(parent
, SWT
.NULL
);
197 panel
.setLayout(new GridLayout());
199 if (configuredRemotes
!= null)
200 createRemotePanel(panel
);
201 createUriPanel(panel
);
203 updateRemoteAndURIPanels();
208 private void createRemotePanel(final Composite parent
) {
209 remoteButton
= new Button(parent
, SWT
.RADIO
);
211 .setText(UIText
.RepositorySelectionPage_configuredRemoteChoice
213 remoteButton
.setSelection(true);
215 remotePanel
= new Composite(parent
, SWT
.NULL
);
216 remotePanel
.setLayout(new GridLayout());
217 final GridData gd
= new GridData();
218 gd
.grabExcessHorizontalSpace
= true;
219 gd
.horizontalAlignment
= SWT
.FILL
;
220 remotePanel
.setLayoutData(gd
);
222 remoteCombo
= new Combo(remotePanel
, SWT
.READ_ONLY
| SWT
.DROP_DOWN
);
223 final String items
[] = new String
[configuredRemotes
.size()];
225 for (final RemoteConfig rc
: configuredRemotes
)
226 items
[i
++] = getTextForRemoteConfig(rc
);
227 final int defaultIndex
= configuredRemotes
.indexOf(remoteConfig
);
228 remoteCombo
.setItems(items
);
229 remoteCombo
.select(defaultIndex
);
230 remoteCombo
.addSelectionListener(new SelectionAdapter() {
232 public void widgetSelected(SelectionEvent e
) {
233 final int idx
= remoteCombo
.getSelectionIndex();
234 remoteConfig
= configuredRemotes
.get(idx
);
240 private void createUriPanel(final Composite parent
) {
241 if (configuredRemotes
!= null) {
242 uriButton
= new Button(parent
, SWT
.RADIO
);
243 uriButton
.setText(UIText
.RepositorySelectionPage_uriChoice
+ ":");
244 uriButton
.addSelectionListener(new SelectionAdapter() {
245 public void widgetSelected(SelectionEvent e
) {
246 // occurs either on selection or unselection event
247 updateRemoteAndURIPanels();
253 uriPanel
= new Composite(parent
, SWT
.NULL
);
254 uriPanel
.setLayout(new GridLayout());
255 final GridData gd
= new GridData();
256 gd
.grabExcessHorizontalSpace
= true;
257 gd
.horizontalAlignment
= SWT
.FILL
;
258 uriPanel
.setLayoutData(gd
);
260 createLocationGroup(uriPanel
);
261 createConnectionGroup(uriPanel
);
262 authGroup
= createAuthenticationGroup(uriPanel
);
265 private void createLocationGroup(final Composite parent
) {
266 final Group g
= createGroup(parent
,
267 UIText
.RepositorySelectionPage_groupLocation
);
269 newLabel(g
, UIText
.RepositorySelectionPage_promptURI
+ ":");
270 uriText
= new Text(g
, SWT
.BORDER
);
271 uriText
.setLayoutData(createFieldGridData());
272 uriText
.addModifyListener(new ModifyListener() {
273 public void modifyText(final ModifyEvent e
) {
279 final URIish u
= new URIish(uriText
.getText());
280 safeSet(hostText
, u
.getHost());
281 safeSet(pathText
, u
.getPath());
282 safeSet(userText
, u
.getUser());
283 safeSet(passText
, u
.getPass());
286 portText
.setText(Integer
.toString(u
.getPort()));
288 portText
.setText("");
291 scheme
.select(S_FILE
);
293 scheme
.select(S_SSH
);
295 for (int i
= 0; i
< DEFAULT_SCHEMES
.length
; i
++) {
296 if (DEFAULT_SCHEMES
[i
].equals(u
.getScheme())) {
305 } catch (URISyntaxException err
) {
306 // leave uriText as it is, but clean up underlying uri and
309 hostText
.setText("");
310 pathText
.setText("");
311 userText
.setText("");
312 passText
.setText("");
313 portText
.setText("");
322 newLabel(g
, UIText
.RepositorySelectionPage_promptHost
+ ":");
323 hostText
= new Text(g
, SWT
.BORDER
);
324 hostText
.setLayoutData(createFieldGridData());
325 hostText
.addModifyListener(new ModifyListener() {
326 public void modifyText(final ModifyEvent e
) {
327 setURI(uri
.setHost(nullString(hostText
.getText())));
331 newLabel(g
, UIText
.RepositorySelectionPage_promptPath
+ ":");
332 pathText
= new Text(g
, SWT
.BORDER
);
333 pathText
.setLayoutData(createFieldGridData());
334 pathText
.addModifyListener(new ModifyListener() {
335 public void modifyText(final ModifyEvent e
) {
336 setURI(uri
.setPath(nullString(pathText
.getText())));
341 private Group
createAuthenticationGroup(final Composite parent
) {
342 final Group g
= createGroup(parent
,
343 UIText
.RepositorySelectionPage_groupAuthentication
);
345 newLabel(g
, UIText
.RepositorySelectionPage_promptUser
+ ":");
346 userText
= new Text(g
, SWT
.BORDER
);
347 userText
.setLayoutData(createFieldGridData());
348 userText
.addModifyListener(new ModifyListener() {
349 public void modifyText(final ModifyEvent e
) {
350 setURI(uri
.setUser(nullString(userText
.getText())));
354 newLabel(g
, UIText
.RepositorySelectionPage_promptPassword
+ ":");
355 passText
= new Text(g
, SWT
.BORDER
| SWT
.PASSWORD
);
356 passText
.setLayoutData(createFieldGridData());
360 private void createConnectionGroup(final Composite parent
) {
361 final Group g
= createGroup(parent
,
362 UIText
.RepositorySelectionPage_groupConnection
);
364 newLabel(g
, UIText
.RepositorySelectionPage_promptScheme
+ ":");
365 scheme
= new Combo(g
, SWT
.DROP_DOWN
| SWT
.READ_ONLY
);
366 scheme
.setItems(DEFAULT_SCHEMES
);
367 scheme
.addSelectionListener(new SelectionAdapter() {
368 public void widgetSelected(final SelectionEvent e
) {
369 final int idx
= scheme
.getSelectionIndex();
371 setURI(uri
.setScheme(null));
373 setURI(uri
.setScheme(nullString(scheme
.getItem(idx
))));
378 newLabel(g
, UIText
.RepositorySelectionPage_promptPort
+ ":");
379 portText
= new Text(g
, SWT
.BORDER
);
380 portText
.addVerifyListener(new VerifyListener() {
381 final Pattern p
= Pattern
.compile("^(?:[1-9][0-9]*)?$");
383 public void verifyText(final VerifyEvent e
) {
384 final String v
= portText
.getText();
386 v
.substring(0, e
.start
) + e
.text
+ v
.substring(e
.end
))
390 portText
.addModifyListener(new ModifyListener() {
391 public void modifyText(final ModifyEvent e
) {
392 final String val
= nullString(portText
.getText());
394 setURI(uri
.setPort(-1));
397 setURI(uri
.setPort(Integer
.parseInt(val
)));
398 } catch (NumberFormatException err
) {
399 // Ignore it for now.
406 private static Group
createGroup(final Composite parent
, final String text
) {
407 final Group g
= new Group(parent
, SWT
.NONE
);
408 final GridLayout layout
= new GridLayout();
409 layout
.numColumns
= 2;
412 final GridData gd
= new GridData();
413 gd
.grabExcessHorizontalSpace
= true;
414 gd
.horizontalAlignment
= SWT
.FILL
;
419 private static void newLabel(final Group g
, final String text
) {
420 new Label(g
, SWT
.NULL
).setText(text
);
423 private static GridData
createFieldGridData() {
424 return new GridData(SWT
.FILL
, SWT
.DEFAULT
, true, false);
427 private static boolean isGIT(final URIish uri
) {
428 return "git".equals(uri
.getScheme());
431 private static boolean isFile(final URIish uri
) {
432 if ("file".equals(uri
.getScheme()) || uri
.getScheme() == null)
434 if (uri
.getHost() != null || uri
.getPort() > 0 || uri
.getUser() != null
435 || uri
.getPass() != null || uri
.getPath() == null)
437 if (uri
.getScheme() == null)
438 return FS
.resolve(new File("."), uri
.getPath()).isDirectory();
442 private static boolean isSSH(final URIish uri
) {
445 final String scheme
= uri
.getScheme();
446 if ("ssh".equals(scheme
))
448 if ("ssh+git".equals(scheme
))
450 if ("git+ssh".equals(scheme
))
452 if (scheme
== null && uri
.getHost() != null && uri
.getPath() != null)
457 private static String
nullString(final String value
) {
460 final String v
= value
.trim();
461 return v
.length() == 0 ?
null : v
;
464 private static void safeSet(final Text text
, final String value
) {
465 text
.setText(value
!= null ? value
: "");
468 private boolean isURISelected() {
469 return configuredRemotes
== null || uriButton
.getSelection();
472 private void setURI(final URIish u
) {
475 if (eventDepth
== 1) {
477 uriText
.setText(uri
.toString());
485 private static void removeUnusableRemoteConfigs(
486 final List
<RemoteConfig
> remotes
) {
487 final Iterator
<RemoteConfig
> iter
= remotes
.iterator();
488 while (iter
.hasNext()) {
489 final RemoteConfig rc
= iter
.next();
490 if (rc
.getURIs().isEmpty())
495 private RemoteConfig
selectDefaultRemoteConfig() {
496 for (final RemoteConfig rc
: configuredRemotes
)
497 if (getTextForRemoteConfig(rc
) == DEFAULT_REMOTE_NAME
)
499 return configuredRemotes
.get(0);
502 private static String
getTextForRemoteConfig(final RemoteConfig rc
) {
503 final StringBuilder sb
= new StringBuilder(rc
.getName());
505 boolean first
= true;
506 for (final URIish u
: rc
.getURIs()) {
507 final String uString
= u
.toString();
512 if (sb
.length() + uString
.length() > REMOTE_CONFIG_TEXT_MAX_LENGTH
) {
519 return sb
.toString();
522 private void checkPage() {
523 if (isURISelected()) {
525 if (uriText
.getText().length() == 0) {
526 selectionIncomplete(null);
531 final URIish finalURI
= new URIish(uriText
.getText());
532 String proto
= finalURI
.getScheme();
533 if (proto
== null && scheme
.getSelectionIndex() >= 0)
534 proto
= scheme
.getItem(scheme
.getSelectionIndex());
536 if (uri
.getPath() == null) {
537 selectionIncomplete(NLS
.bind(
538 UIText
.RepositorySelectionPage_fieldRequired
,
539 unamp(UIText
.RepositorySelectionPage_promptPath
), proto
));
543 if (isFile(finalURI
)) {
544 String badField
= null;
545 if (uri
.getHost() != null)
546 badField
= UIText
.RepositorySelectionPage_promptHost
;
547 else if (uri
.getUser() != null)
548 badField
= UIText
.RepositorySelectionPage_promptUser
;
549 else if (uri
.getPass() != null)
550 badField
= UIText
.RepositorySelectionPage_promptPassword
;
551 if (badField
!= null) {
552 selectionIncomplete(NLS
554 UIText
.RepositorySelectionPage_fieldNotSupported
,
555 unamp(badField
), proto
));
559 final File d
= FS
.resolve(new File("."), uri
.getPath());
561 selectionIncomplete(NLS
.bind(
562 UIText
.RepositorySelectionPage_fileNotFound
, d
563 .getAbsolutePath()));
567 selectionComplete(finalURI
, null);
571 if (uri
.getHost() == null) {
572 selectionIncomplete(NLS
.bind(
573 UIText
.RepositorySelectionPage_fieldRequired
,
574 unamp(UIText
.RepositorySelectionPage_promptHost
), proto
));
578 if (isGIT(finalURI
)) {
579 String badField
= null;
580 if (uri
.getUser() != null)
581 badField
= UIText
.RepositorySelectionPage_promptUser
;
582 else if (uri
.getPass() != null)
583 badField
= UIText
.RepositorySelectionPage_promptPassword
;
584 if (badField
!= null) {
585 selectionIncomplete(NLS
587 UIText
.RepositorySelectionPage_fieldNotSupported
,
588 unamp(badField
), proto
));
593 selectionComplete(finalURI
, null);
595 } catch (URISyntaxException e
) {
596 selectionIncomplete(e
.getReason());
598 } catch (Exception e
) {
599 Activator
.logError("Error validating " + getClass().getName(),
601 selectionIncomplete(UIText
.RepositorySelectionPage_internalError
);
605 assert remoteButton
.getSelection();
606 selectionComplete(null, remoteConfig
);
611 private String
unamp(String s
) {
612 return s
.replace("&","");
615 private void selectionIncomplete(final String errorMessage
) {
616 setExposedSelection(null, null);
617 setErrorMessage(errorMessage
);
618 setPageComplete(false);
621 private void selectionComplete(final URIish u
, final RemoteConfig rc
) {
622 setExposedSelection(u
, rc
);
623 setErrorMessage(null);
624 setPageComplete(true);
627 private void setExposedSelection(final URIish u
, final RemoteConfig rc
) {
628 final RepositorySelection newSelection
= new RepositorySelection(u
, rc
);
629 if (newSelection
.equals(selection
))
632 selection
= newSelection
;
633 notifySelectionChanged();
636 private void updateRemoteAndURIPanels() {
637 setEnabledRecursively(uriPanel
, isURISelected());
638 if (uriPanel
.getEnabled())
640 if (configuredRemotes
!= null)
641 setEnabledRecursively(remotePanel
, !isURISelected());
644 private void updateAuthGroup() {
645 switch (scheme
.getSelectionIndex()) {
647 hostText
.setEnabled(true);
648 portText
.setEnabled(true);
649 setEnabledRecursively(authGroup
, false);
656 hostText
.setEnabled(true);
657 portText
.setEnabled(true);
658 setEnabledRecursively(authGroup
, true);
661 hostText
.setEnabled(false);
662 portText
.setEnabled(false);
663 setEnabledRecursively(authGroup
, false);
669 public void setVisible(boolean visible
) {
670 super.setVisible(visible
);