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 * which accompanies this distribution, and is available at
10 * http://www.eclipse.org/legal/epl-v10.html
11 *******************************************************************************/
12 package org
.eclipse
.egit
.ui
.internal
.components
;
15 import java
.net
.URISyntaxException
;
16 import java
.util
.Iterator
;
17 import java
.util
.List
;
18 import java
.util
.regex
.Pattern
;
20 import org
.eclipse
.egit
.ui
.Activator
;
21 import org
.eclipse
.egit
.ui
.UIText
;
22 import org
.eclipse
.osgi
.util
.NLS
;
23 import org
.eclipse
.swt
.SWT
;
24 import org
.eclipse
.swt
.events
.ModifyEvent
;
25 import org
.eclipse
.swt
.events
.ModifyListener
;
26 import org
.eclipse
.swt
.events
.SelectionAdapter
;
27 import org
.eclipse
.swt
.events
.SelectionEvent
;
28 import org
.eclipse
.swt
.events
.VerifyEvent
;
29 import org
.eclipse
.swt
.events
.VerifyListener
;
30 import org
.eclipse
.swt
.layout
.GridData
;
31 import org
.eclipse
.swt
.layout
.GridLayout
;
32 import org
.eclipse
.swt
.widgets
.Button
;
33 import org
.eclipse
.swt
.widgets
.Combo
;
34 import org
.eclipse
.swt
.widgets
.Composite
;
35 import org
.eclipse
.swt
.widgets
.Control
;
36 import org
.eclipse
.swt
.widgets
.Group
;
37 import org
.eclipse
.swt
.widgets
.Label
;
38 import org
.eclipse
.swt
.widgets
.Text
;
39 import org
.eclipse
.jgit
.transport
.RemoteConfig
;
40 import org
.eclipse
.jgit
.transport
.URIish
;
41 import org
.eclipse
.jgit
.util
.FS
;
44 * Wizard page that allows the user entering the location of a remote repository
45 * by specifying URL manually or selecting a preconfigured remote repository.
47 public class RepositorySelectionPage
extends BaseWizardPage
{
48 private static final int REMOTE_CONFIG_TEXT_MAX_LENGTH
= 80;
50 private static final String DEFAULT_REMOTE_NAME
= "origin";
52 private static final int S_GIT
= 0;
54 private static final int S_SSH
= 1;
56 private static final int S_SFTP
= 2;
58 private static final int S_HTTP
= 3;
60 private static final int S_HTTPS
= 4;
62 private static final int S_FTP
= 5;
64 private static final int S_FILE
= 6;
66 private static final String
[] DEFAULT_SCHEMES
;
68 DEFAULT_SCHEMES
= new String
[7];
69 DEFAULT_SCHEMES
[S_GIT
] = "git";
70 DEFAULT_SCHEMES
[S_SSH
] = "git+ssh";
71 DEFAULT_SCHEMES
[S_SFTP
] = "sftp";
72 DEFAULT_SCHEMES
[S_HTTP
] = "http";
73 DEFAULT_SCHEMES
[S_HTTPS
] = "https";
74 DEFAULT_SCHEMES
[S_FTP
] = "ftp";
75 DEFAULT_SCHEMES
[S_FILE
] = "file";
78 private static void setEnabledRecursively(final Control control
,
79 final boolean enable
) {
80 control
.setEnabled(enable
);
81 if (control
instanceof Composite
)
82 for (final Control child
: ((Composite
) control
).getChildren())
83 setEnabledRecursively(child
, enable
);
86 private final List
<RemoteConfig
> configuredRemotes
;
88 private Group authGroup
;
92 private Text hostText
;
94 private Text pathText
;
96 private Text userText
;
98 private Text passText
;
100 private Combo scheme
;
102 private Text portText
;
104 private int eventDepth
;
108 private RemoteConfig remoteConfig
;
110 private RepositorySelection selection
;
112 private Composite remotePanel
;
114 private Button remoteButton
;
116 private Combo remoteCombo
;
118 private Composite uriPanel
;
120 private Button uriButton
;
123 * Create repository selection page, allowing user specifying URI or
124 * (optionally) choosing from preconfigured remotes list.
126 * Wizard page is created without image, just with text description.
128 * @param sourceSelection
129 * true if dialog is used for source selection; false otherwise
130 * (destination selection). This indicates appropriate text
132 * @param configuredRemotes
133 * list of configured remotes that user may select as an
134 * alternative to manual URI specification. Remotes appear in
135 * given order in GUI, with {@value #DEFAULT_REMOTE_NAME} as the
136 * default choice. List may be null or empty - no remotes
137 * configurations appear in this case. Note that the provided
138 * list may be changed by this constructor.
140 public RepositorySelectionPage(final boolean sourceSelection
,
141 final List
<RemoteConfig
> configuredRemotes
) {
142 super(RepositorySelectionPage
.class.getName());
143 this.uri
= new URIish();
145 if (configuredRemotes
!= null)
146 removeUnusableRemoteConfigs(configuredRemotes
);
147 if (configuredRemotes
== null || configuredRemotes
.isEmpty())
148 this.configuredRemotes
= null;
150 this.configuredRemotes
= configuredRemotes
;
151 this.remoteConfig
= selectDefaultRemoteConfig();
153 selection
= RepositorySelection
.INVALID_SELECTION
;
155 if (sourceSelection
) {
156 setTitle(UIText
.RepositorySelectionPage_sourceSelectionTitle
);
157 setDescription(UIText
.RepositorySelectionPage_sourceSelectionDescription
);
159 setTitle(UIText
.RepositorySelectionPage_destinationSelectionTitle
);
160 setDescription(UIText
.RepositorySelectionPage_destinationSelectionDescription
);
165 * Create repository selection page, allowing user specifying URI, with no
166 * preconfigured remotes selection.
168 * @param sourceSelection
169 * true if dialog is used for source selection; false otherwise
170 * (destination selection). This indicates appropriate text
173 public RepositorySelectionPage(final boolean sourceSelection
) {
174 this(sourceSelection
, null);
178 * @return repository selection representing current page state.
180 public RepositorySelection
getSelection() {
185 * Compare current repository selection set by user to provided one.
188 * repository selection to compare.
189 * @return true if provided selection is equal to current page selection,
192 public boolean selectionEquals(final RepositorySelection s
) {
193 return selection
.equals(s
);
196 public void createControl(final Composite parent
) {
197 final Composite panel
= new Composite(parent
, SWT
.NULL
);
198 panel
.setLayout(new GridLayout());
200 if (configuredRemotes
!= null)
201 createRemotePanel(panel
);
202 createUriPanel(panel
);
204 updateRemoteAndURIPanels();
209 private void createRemotePanel(final Composite parent
) {
210 remoteButton
= new Button(parent
, SWT
.RADIO
);
212 .setText(UIText
.RepositorySelectionPage_configuredRemoteChoice
214 remoteButton
.setSelection(true);
216 remotePanel
= new Composite(parent
, SWT
.NULL
);
217 remotePanel
.setLayout(new GridLayout());
218 final GridData gd
= new GridData();
219 gd
.grabExcessHorizontalSpace
= true;
220 gd
.horizontalAlignment
= SWT
.FILL
;
221 remotePanel
.setLayoutData(gd
);
223 remoteCombo
= new Combo(remotePanel
, SWT
.READ_ONLY
| SWT
.DROP_DOWN
);
224 final String items
[] = new String
[configuredRemotes
.size()];
226 for (final RemoteConfig rc
: configuredRemotes
)
227 items
[i
++] = getTextForRemoteConfig(rc
);
228 final int defaultIndex
= configuredRemotes
.indexOf(remoteConfig
);
229 remoteCombo
.setItems(items
);
230 remoteCombo
.select(defaultIndex
);
231 remoteCombo
.addSelectionListener(new SelectionAdapter() {
233 public void widgetSelected(SelectionEvent e
) {
234 final int idx
= remoteCombo
.getSelectionIndex();
235 remoteConfig
= configuredRemotes
.get(idx
);
241 private void createUriPanel(final Composite parent
) {
242 if (configuredRemotes
!= null) {
243 uriButton
= new Button(parent
, SWT
.RADIO
);
244 uriButton
.setText(UIText
.RepositorySelectionPage_uriChoice
+ ":");
245 uriButton
.addSelectionListener(new SelectionAdapter() {
246 public void widgetSelected(SelectionEvent e
) {
247 // occurs either on selection or unselection event
248 updateRemoteAndURIPanels();
254 uriPanel
= new Composite(parent
, SWT
.NULL
);
255 uriPanel
.setLayout(new GridLayout());
256 final GridData gd
= new GridData();
257 gd
.grabExcessHorizontalSpace
= true;
258 gd
.horizontalAlignment
= SWT
.FILL
;
259 uriPanel
.setLayoutData(gd
);
261 createLocationGroup(uriPanel
);
262 createConnectionGroup(uriPanel
);
263 authGroup
= createAuthenticationGroup(uriPanel
);
266 private void createLocationGroup(final Composite parent
) {
267 final Group g
= createGroup(parent
,
268 UIText
.RepositorySelectionPage_groupLocation
);
270 newLabel(g
, UIText
.RepositorySelectionPage_promptURI
+ ":");
271 uriText
= new Text(g
, SWT
.BORDER
);
272 uriText
.setLayoutData(createFieldGridData());
273 uriText
.addModifyListener(new ModifyListener() {
274 public void modifyText(final ModifyEvent e
) {
280 final URIish u
= new URIish(uriText
.getText());
281 safeSet(hostText
, u
.getHost());
282 safeSet(pathText
, u
.getPath());
283 safeSet(userText
, u
.getUser());
284 safeSet(passText
, u
.getPass());
287 portText
.setText(Integer
.toString(u
.getPort()));
289 portText
.setText("");
292 scheme
.select(S_FILE
);
294 scheme
.select(S_SSH
);
296 for (int i
= 0; i
< DEFAULT_SCHEMES
.length
; i
++) {
297 if (DEFAULT_SCHEMES
[i
].equals(u
.getScheme())) {
306 } catch (URISyntaxException err
) {
307 // leave uriText as it is, but clean up underlying uri and
310 hostText
.setText("");
311 pathText
.setText("");
312 userText
.setText("");
313 passText
.setText("");
314 portText
.setText("");
323 newLabel(g
, UIText
.RepositorySelectionPage_promptHost
+ ":");
324 hostText
= new Text(g
, SWT
.BORDER
);
325 hostText
.setLayoutData(createFieldGridData());
326 hostText
.addModifyListener(new ModifyListener() {
327 public void modifyText(final ModifyEvent e
) {
328 setURI(uri
.setHost(nullString(hostText
.getText())));
332 newLabel(g
, UIText
.RepositorySelectionPage_promptPath
+ ":");
333 pathText
= new Text(g
, SWT
.BORDER
);
334 pathText
.setLayoutData(createFieldGridData());
335 pathText
.addModifyListener(new ModifyListener() {
336 public void modifyText(final ModifyEvent e
) {
337 setURI(uri
.setPath(nullString(pathText
.getText())));
342 private Group
createAuthenticationGroup(final Composite parent
) {
343 final Group g
= createGroup(parent
,
344 UIText
.RepositorySelectionPage_groupAuthentication
);
346 newLabel(g
, UIText
.RepositorySelectionPage_promptUser
+ ":");
347 userText
= new Text(g
, SWT
.BORDER
);
348 userText
.setLayoutData(createFieldGridData());
349 userText
.addModifyListener(new ModifyListener() {
350 public void modifyText(final ModifyEvent e
) {
351 setURI(uri
.setUser(nullString(userText
.getText())));
355 newLabel(g
, UIText
.RepositorySelectionPage_promptPassword
+ ":");
356 passText
= new Text(g
, SWT
.BORDER
| SWT
.PASSWORD
);
357 passText
.setLayoutData(createFieldGridData());
361 private void createConnectionGroup(final Composite parent
) {
362 final Group g
= createGroup(parent
,
363 UIText
.RepositorySelectionPage_groupConnection
);
365 newLabel(g
, UIText
.RepositorySelectionPage_promptScheme
+ ":");
366 scheme
= new Combo(g
, SWT
.DROP_DOWN
| SWT
.READ_ONLY
);
367 scheme
.setItems(DEFAULT_SCHEMES
);
368 scheme
.addSelectionListener(new SelectionAdapter() {
369 public void widgetSelected(final SelectionEvent e
) {
370 final int idx
= scheme
.getSelectionIndex();
372 setURI(uri
.setScheme(null));
374 setURI(uri
.setScheme(nullString(scheme
.getItem(idx
))));
379 newLabel(g
, UIText
.RepositorySelectionPage_promptPort
+ ":");
380 portText
= new Text(g
, SWT
.BORDER
);
381 portText
.addVerifyListener(new VerifyListener() {
382 final Pattern p
= Pattern
.compile("^(?:[1-9][0-9]*)?$");
384 public void verifyText(final VerifyEvent e
) {
385 final String v
= portText
.getText();
387 v
.substring(0, e
.start
) + e
.text
+ v
.substring(e
.end
))
391 portText
.addModifyListener(new ModifyListener() {
392 public void modifyText(final ModifyEvent e
) {
393 final String val
= nullString(portText
.getText());
395 setURI(uri
.setPort(-1));
398 setURI(uri
.setPort(Integer
.parseInt(val
)));
399 } catch (NumberFormatException err
) {
400 // Ignore it for now.
407 private static Group
createGroup(final Composite parent
, final String text
) {
408 final Group g
= new Group(parent
, SWT
.NONE
);
409 final GridLayout layout
= new GridLayout();
410 layout
.numColumns
= 2;
413 final GridData gd
= new GridData();
414 gd
.grabExcessHorizontalSpace
= true;
415 gd
.horizontalAlignment
= SWT
.FILL
;
420 private static void newLabel(final Group g
, final String text
) {
421 new Label(g
, SWT
.NULL
).setText(text
);
424 private static GridData
createFieldGridData() {
425 return new GridData(SWT
.FILL
, SWT
.DEFAULT
, true, false);
428 private static boolean isGIT(final URIish uri
) {
429 return "git".equals(uri
.getScheme());
432 private static boolean isFile(final URIish uri
) {
433 if ("file".equals(uri
.getScheme()) || uri
.getScheme() == null)
435 if (uri
.getHost() != null || uri
.getPort() > 0 || uri
.getUser() != null
436 || uri
.getPass() != null || uri
.getPath() == null)
438 if (uri
.getScheme() == null)
439 return FS
.resolve(new File("."), uri
.getPath()).isDirectory();
443 private static boolean isSSH(final URIish uri
) {
446 final String scheme
= uri
.getScheme();
447 if ("ssh".equals(scheme
))
449 if ("ssh+git".equals(scheme
))
451 if ("git+ssh".equals(scheme
))
453 if (scheme
== null && uri
.getHost() != null && uri
.getPath() != null)
458 private static String
nullString(final String value
) {
461 final String v
= value
.trim();
462 return v
.length() == 0 ?
null : v
;
465 private static void safeSet(final Text text
, final String value
) {
466 text
.setText(value
!= null ? value
: "");
469 private boolean isURISelected() {
470 return configuredRemotes
== null || uriButton
.getSelection();
473 private void setURI(final URIish u
) {
476 if (eventDepth
== 1) {
478 uriText
.setText(uri
.toString());
486 private static void removeUnusableRemoteConfigs(
487 final List
<RemoteConfig
> remotes
) {
488 final Iterator
<RemoteConfig
> iter
= remotes
.iterator();
489 while (iter
.hasNext()) {
490 final RemoteConfig rc
= iter
.next();
491 if (rc
.getURIs().isEmpty())
496 private RemoteConfig
selectDefaultRemoteConfig() {
497 for (final RemoteConfig rc
: configuredRemotes
)
498 if (getTextForRemoteConfig(rc
) == DEFAULT_REMOTE_NAME
)
500 return configuredRemotes
.get(0);
503 private static String
getTextForRemoteConfig(final RemoteConfig rc
) {
504 final StringBuilder sb
= new StringBuilder(rc
.getName());
506 boolean first
= true;
507 for (final URIish u
: rc
.getURIs()) {
508 final String uString
= u
.toString();
513 if (sb
.length() + uString
.length() > REMOTE_CONFIG_TEXT_MAX_LENGTH
) {
520 return sb
.toString();
523 private void checkPage() {
524 if (isURISelected()) {
526 if (uriText
.getText().length() == 0) {
527 selectionIncomplete(null);
532 final URIish finalURI
= new URIish(uriText
.getText());
533 String proto
= finalURI
.getScheme();
534 if (proto
== null && scheme
.getSelectionIndex() >= 0)
535 proto
= scheme
.getItem(scheme
.getSelectionIndex());
537 if (uri
.getPath() == null) {
538 selectionIncomplete(NLS
.bind(
539 UIText
.RepositorySelectionPage_fieldRequired
,
540 unamp(UIText
.RepositorySelectionPage_promptPath
), proto
));
544 if (isFile(finalURI
)) {
545 String badField
= null;
546 if (uri
.getHost() != null)
547 badField
= UIText
.RepositorySelectionPage_promptHost
;
548 else if (uri
.getUser() != null)
549 badField
= UIText
.RepositorySelectionPage_promptUser
;
550 else if (uri
.getPass() != null)
551 badField
= UIText
.RepositorySelectionPage_promptPassword
;
552 if (badField
!= null) {
553 selectionIncomplete(NLS
555 UIText
.RepositorySelectionPage_fieldNotSupported
,
556 unamp(badField
), proto
));
560 final File d
= FS
.resolve(new File("."), uri
.getPath());
562 selectionIncomplete(NLS
.bind(
563 UIText
.RepositorySelectionPage_fileNotFound
, d
564 .getAbsolutePath()));
568 selectionComplete(finalURI
, null);
572 if (uri
.getHost() == null) {
573 selectionIncomplete(NLS
.bind(
574 UIText
.RepositorySelectionPage_fieldRequired
,
575 unamp(UIText
.RepositorySelectionPage_promptHost
), proto
));
579 if (isGIT(finalURI
)) {
580 String badField
= null;
581 if (uri
.getUser() != null)
582 badField
= UIText
.RepositorySelectionPage_promptUser
;
583 else if (uri
.getPass() != null)
584 badField
= UIText
.RepositorySelectionPage_promptPassword
;
585 if (badField
!= null) {
586 selectionIncomplete(NLS
588 UIText
.RepositorySelectionPage_fieldNotSupported
,
589 unamp(badField
), proto
));
594 selectionComplete(finalURI
, null);
596 } catch (URISyntaxException e
) {
597 selectionIncomplete(e
.getReason());
599 } catch (Exception e
) {
600 Activator
.logError("Error validating " + getClass().getName(),
602 selectionIncomplete(UIText
.RepositorySelectionPage_internalError
);
606 assert remoteButton
.getSelection();
607 selectionComplete(null, remoteConfig
);
612 private String
unamp(String s
) {
613 return s
.replace("&","");
616 private void selectionIncomplete(final String errorMessage
) {
617 setExposedSelection(null, null);
618 setErrorMessage(errorMessage
);
619 setPageComplete(false);
622 private void selectionComplete(final URIish u
, final RemoteConfig rc
) {
623 setExposedSelection(u
, rc
);
624 setErrorMessage(null);
625 setPageComplete(true);
628 private void setExposedSelection(final URIish u
, final RemoteConfig rc
) {
629 final RepositorySelection newSelection
= new RepositorySelection(u
, rc
);
630 if (newSelection
.equals(selection
))
633 selection
= newSelection
;
634 notifySelectionChanged();
637 private void updateRemoteAndURIPanels() {
638 setEnabledRecursively(uriPanel
, isURISelected());
639 if (uriPanel
.getEnabled())
641 if (configuredRemotes
!= null)
642 setEnabledRecursively(remotePanel
, !isURISelected());
645 private void updateAuthGroup() {
646 switch (scheme
.getSelectionIndex()) {
648 hostText
.setEnabled(true);
649 portText
.setEnabled(true);
650 setEnabledRecursively(authGroup
, false);
657 hostText
.setEnabled(true);
658 portText
.setEnabled(true);
659 setEnabledRecursively(authGroup
, true);
662 hostText
.setEnabled(false);
663 portText
.setEnabled(false);
664 setEnabledRecursively(authGroup
, false);
670 public void setVisible(boolean visible
) {
671 super.setVisible(visible
);