Initial implementation of Clone
[nbgit.git] / src / org / nbgit / ui / repository / GitRepositoryUI.java
blob063d2cf9f49f906c26ed3c8b53edcf36ed7b6fca
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common
8 * Development and Distribution License("CDDL") (collectively, the
9 * "License"). You may not use this file except in compliance with the
10 * License. You can obtain a copy of the License at
11 * http://www.netbeans.org/cddl-gplv2.html
12 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13 * specific language governing permissions and limitations under the
14 * License. When distributing the software, include this License Header
15 * Notice in each file and include the License file at
16 * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17 * particular file as subject to the "Classpath" exception as provided
18 * by Sun in the GPL Version 2 section of the License file that
19 * accompanied this code. If applicable, add the following below the
20 * License Header, with the fields enclosed by brackets [] replaced by
21 * your own identifying information:
22 * "Portions Copyrighted [year] [name of copyright owner]"
24 * Contributor(s):
26 * The Original Software is NetBeans. The Initial Developer of the Original
27 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2009 Sun
28 * Microsystems, Inc. All Rights Reserved.
29 * Portions Copyright 2009 Alexander Coles (Ikonoklastik Productions).
31 * If you wish your version of this file to be governed by only the CDDL
32 * or only the GPL Version 2, indicate your decision by adding
33 * "[Contributor] elects to include this software in this distribution
34 * under the [CDDL or GPL Version 2] license." If you do not indicate a
35 * single choice of license, a recipient has the option to distribute
36 * your version of this file under either the CDDL, the GPL Version 2 or
37 * to extend the choice of license to its licensees as provided above.
38 * However, if you add GPL Version 2 code and therefore, elected the GPL
39 * Version 2 license, then the option applies only if the new code is
40 * made subject to such option by the copyright holder.
42 package org.nbgit.ui.repository;
44 import java.awt.BorderLayout;
45 import java.awt.Component;
46 import java.awt.Dialog;
47 import java.awt.Dimension;
48 import java.awt.EventQueue;
49 import java.awt.event.ActionEvent;
50 import java.awt.event.ActionListener;
51 import java.awt.event.FocusEvent;
52 import java.awt.event.FocusListener;
53 import java.awt.event.ItemEvent;
54 import java.awt.event.ItemListener;
55 import java.lang.ref.Reference;
56 import java.lang.ref.WeakReference;
57 import java.net.MalformedURLException;
58 import java.net.URI;
59 import java.net.URISyntaxException;
60 import java.net.URL;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.List;
64 import java.util.Vector;
65 import javax.swing.ComboBoxEditor;
66 import javax.swing.DefaultComboBoxModel;
67 import javax.swing.JPanel;
68 import javax.swing.event.ChangeEvent;
69 import javax.swing.event.ChangeListener;
70 import javax.swing.event.DocumentEvent;
71 import javax.swing.event.DocumentListener;
72 import javax.swing.text.Document;
73 import javax.swing.text.JTextComponent;
74 import org.nbgit.GitModuleConfig;
75 import org.netbeans.api.options.OptionsDisplayer;
77 import org.netbeans.modules.versioning.util.DialogBoundsPreserver;
78 import org.openide.DialogDescriptor;
79 import org.openide.DialogDisplayer;
80 import org.openide.util.HelpCtx;
81 import org.openide.util.NbBundle;
82 import org.openide.util.Utilities;
83 import org.spearce.jgit.transport.Transport;
84 import org.spearce.jgit.transport.URIish;
86 /**
87 * @author Tomas Stupka
88 * @author Marian Petras
90 public class GitRepositoryUI implements ActionListener, FocusListener, ItemListener {
92 public final static int FLAG_URL_ENABLED = 4;
93 public final static int FLAG_ACCEPT_REVISION = 8;
94 public final static int FLAG_SHOW_HINTS = 32;
95 public final static int FLAG_SHOW_PROXY = 64;
97 private final static String LOCAL_URL_HELP = "file:///repository_path"; // NOI18N
98 private final static String HTTP_URL_HELP = Utilities.isWindows()?
99 "http://[DOMAIN%5C]hostname/repository_path": // NOI18N
100 "http://hostname/repository_path"; // NOI18N
101 private final static String HTTPS_URL_HELP = Utilities.isWindows()?
102 "https://[DOMAIN%5C]hostname/repository_path": // NOI18N
103 "https://hostname/repository_path"; // NOI18N
104 private final static String GIT_URL_HELP = "git://hostname/repository_path"; // NOI18N
105 private final static String GIT_SSH_URL_HELP = "git@hostname:repository_path"; // NOI18N
106 private final static String SSH_URL_HELP = "ssh://hostname/repository_path"; // NOI18N
108 private RepositoryPanel repositoryPanel;
109 private boolean valid = true;
110 private List<ChangeListener> listeners;
111 private final ChangeEvent changeEvent = new ChangeEvent(this);
113 private Transport repositoryConnection;
114 private URIish url;
116 public static final String PROP_VALID = "valid"; // NOI18N
118 private String message;
119 private int modeMask;
120 private Dimension maxNeededSize;
121 private boolean bPushPull;
122 private static int GIT_PUSH_PULL_VERT_PADDING = 30;
124 private JTextComponent urlComboEditor;
125 private Document urlDoc, usernameDoc, passwordDoc, tunnelCmdDoc;
126 private boolean urlBeingSelectedFromPopup = false;
128 public GitRepositoryUI(int modeMask, String titleLabel, boolean bPushPull) {
130 this.modeMask = modeMask;
132 initPanel();
134 repositoryPanel.titleLabel.setText(titleLabel);
136 repositoryPanel.urlComboBox.setEnabled(isSet(FLAG_URL_ENABLED));
137 repositoryPanel.tunnelHelpLabel.setVisible(isSet(FLAG_SHOW_HINTS));
138 repositoryPanel.tipLabel.setVisible(isSet(FLAG_SHOW_HINTS));
140 //repositoryPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 0));
142 // retrieve the dialog size for the largest configuration
143 if(bPushPull)
144 updateVisibility("foo:"); // NOI18N
145 else
146 updateVisibility("https:"); // NOI18N
147 maxNeededSize = repositoryPanel.getPreferredSize();
149 //TODO: implement this
150 //repositoryPanel.savePasswordCheckBox.setSelected(GitModuleConfig.getDefault().getSavePassword());
151 repositoryPanel.schedulePostInitRoutine(new Runnable() {
152 public void run() {
153 refreshUrlHistory();
158 public void actionPerformed(ActionEvent e) {
159 assert e.getSource() == repositoryPanel.proxySettingsButton;
161 onProxyConfiguration();
164 private void onProxyConfiguration() {
165 OptionsDisplayer.getDefault().open("General"); // NOI18N
168 private void initPanel() {
169 repositoryPanel = new RepositoryPanel();
171 urlComboEditor = (JTextComponent) repositoryPanel.urlComboBox
172 .getEditor().getEditorComponent();
173 urlDoc = urlComboEditor.getDocument();
174 usernameDoc = repositoryPanel.userTextField.getDocument();
175 passwordDoc = repositoryPanel.userPasswordField.getDocument();
176 tunnelCmdDoc = repositoryPanel.tunnelCommandTextField.getDocument();
178 DocumentListener documentListener = new DocumentChangeHandler();
179 urlDoc.addDocumentListener(documentListener);
180 passwordDoc.addDocumentListener(documentListener);
181 usernameDoc.addDocumentListener(documentListener);
182 tunnelCmdDoc.addDocumentListener(documentListener);
184 repositoryPanel.savePasswordCheckBox.addItemListener(this);
185 repositoryPanel.urlComboBox.addItemListener(this);
187 repositoryPanel.proxySettingsButton.addActionListener(this);
189 repositoryPanel.userPasswordField.addFocusListener(this);
191 tweakComboBoxEditor();
194 private void tweakComboBoxEditor() {
195 final ComboBoxEditor origEditor = repositoryPanel.urlComboBox.getEditor();
197 if (origEditor.getClass() == UrlComboBoxEditor.class) {
198 /* attempt to tweak the combo-box multiple times */
199 assert false;
200 return;
203 repositoryPanel.urlComboBox.setEditor(new UrlComboBoxEditor(origEditor));
207 * Customized combo-box editor for displaying/modification of URL
208 * of a Git repository.
209 * It is customized in the following aspects:
210 * <ul>
211 * <li>When a RepositoryConnection is selected, displays its URL
212 * without user data (name and password).</li>
213 * <li>If a {@code RepositoryConnection} is set via method
214 * {@code setItem}, it holds a reference to it until another item
215 * is set via method {@code setItem()} or until the user modifies
216 * the text. This allows method {@code getItem()} to return
217 * the same item ({@code RepositoryConnection}).
218 * The allows the combo-box to correctly detect whether the item
219 * has been changed (since the last call of {@code setItem()}
220 * or not.</li>
221 * </ul>
223 private final class UrlComboBoxEditor implements ComboBoxEditor,
224 DocumentListener {
226 private final ComboBoxEditor origEditor;
227 private Reference<Transport> repoConnRef;
229 private UrlComboBoxEditor(ComboBoxEditor originalEditor) {
230 this.origEditor = originalEditor;
231 ((JTextComponent) originalEditor.getEditorComponent())
232 .getDocument().addDocumentListener(this);
235 public void setItem(Object anObject) {
236 urlBeingSelectedFromPopup = true;
237 try {
238 setItemImpl(anObject);
239 } finally {
240 urlBeingSelectedFromPopup = false;
244 private void setItemImpl(Object anObject) {
245 assert urlBeingSelectedFromPopup;
247 if (anObject instanceof Transport) {
248 Transport repoConn = (Transport) anObject;
249 repoConnRef = new WeakReference<Transport>(repoConn);
250 origEditor.setItem(repoConn.getURI().toString());
251 } else {
252 clearRepoConnRef();
253 origEditor.setItem(anObject);
257 public Component getEditorComponent() {
258 return origEditor.getEditorComponent();
260 public Object getItem() {
261 Transport repoConn = getRepoConn();
262 if (repoConn != null) {
263 return repoConn;
266 return origEditor.getItem();
268 public void selectAll() {
269 origEditor.selectAll();
271 public void addActionListener(ActionListener l) {
272 origEditor.addActionListener(l);
274 public void removeActionListener(ActionListener l) {
275 origEditor.removeActionListener(l);
278 public void insertUpdate(DocumentEvent e) {
279 textChanged();
281 public void removeUpdate(DocumentEvent e) {
282 textChanged();
284 public void changedUpdate(DocumentEvent e) {
285 textChanged();
287 private void textChanged() {
288 if (urlBeingSelectedFromPopup) {
289 return;
291 clearRepoConnRef();
294 private Transport getRepoConn() {
295 if (repoConnRef != null) {
296 Transport repoConn = repoConnRef.get();
297 if (repoConn != null) {
298 return repoConn;
301 return null;
304 private void clearRepoConnRef() {
305 if (repoConnRef != null) {
306 repoConnRef.clear();
312 public void refreshUrlHistory() {
313 repositoryPanel.urlComboBox.setModel(
314 new DefaultComboBoxModel(createPresetComboEntries()));
316 urlComboEditor.selectAll();
319 private Vector<?> createPresetComboEntries() {
320 assert repositoryPanel.urlComboBox.isEditable();
322 Vector<Object> result;
324 List<Transport> recentUrls = new ArrayList<Transport>(); // TODO: implement GitModuleConfig.getDefault().getRecentUrls();
325 GitURIScheme[] schemes = GitURIScheme.values();
327 result = new Vector<Object>(recentUrls.size() + schemes.length);
328 result.addAll(recentUrls);
329 for (GitURIScheme scheme : schemes) {
330 result.add(createURIPrefixForScheme(scheme));
333 return result;
336 private static String createURIPrefixForScheme(GitURIScheme scheme) {
337 if (scheme == GitURIScheme.FILE) {
338 return scheme + ":/"; //NOI18N
339 } else {
340 return scheme + "://"; //NOI18N
344 private final class DocumentChangeHandler implements DocumentListener {
346 DocumentChangeHandler() { }
348 public void insertUpdate(DocumentEvent e) {
349 textChanged(e);
351 public void removeUpdate(DocumentEvent e) {
352 textChanged(e);
354 public void changedUpdate(DocumentEvent e) {
355 textChanged(e);
358 private void textChanged(final DocumentEvent e) {
359 assert EventQueue.isDispatchThread();
361 Document modifiedDocument = e.getDocument();
363 assert modifiedDocument != null;
364 assert (modifiedDocument == urlDoc) || !urlBeingSelectedFromPopup;
366 if (modifiedDocument == urlDoc) {
367 onUrlChange();
368 } else if (modifiedDocument == usernameDoc) {
369 onUsernameChange();
370 } else if (modifiedDocument == passwordDoc) {
371 onPasswordChange();
372 } else if (modifiedDocument == tunnelCmdDoc) {
373 onTunnelCommandChange();
380 * Always updates UI fields visibility.
382 private void onUrlChange() {
383 if (!urlBeingSelectedFromPopup) {
384 repositoryConnection = null;
385 url = null;
387 repositoryPanel.userTextField.setText(null);
388 repositoryPanel.userPasswordField.setText(null);
389 repositoryPanel.tunnelCommandTextField.setText(null);
390 repositoryPanel.savePasswordCheckBox.setSelected(false);
392 // TODO: implementation of validation quickValidateUrl();
393 updateVisibility();
396 private void updateVisibility() {
397 updateVisibility(getUrlString());
400 /** Shows proper fields depending on Git connection method. */
401 private void updateVisibility(String selectedUrlString) {
403 boolean authFields = false;
404 boolean proxyFields = false;
405 boolean sshFields = false;
406 if(selectedUrlString.startsWith("http:")) { // NOI18N
407 repositoryPanel.tipLabel.setText(HTTP_URL_HELP);
408 authFields = true;
409 proxyFields = true;
410 } else if(selectedUrlString.startsWith("https:")) { // NOI18N
411 repositoryPanel.tipLabel.setText(HTTPS_URL_HELP);
412 //authFields = true;
413 proxyFields = true;
414 } else if (selectedUrlString.startsWith("git:")) { // NOI18N
415 repositoryPanel.tipLabel.setText(GIT_URL_HELP);
416 } else if(selectedUrlString.startsWith("git@")) { // NOI18N
417 repositoryPanel.tipLabel.setText(GIT_SSH_URL_HELP);
418 authFields = true;
419 proxyFields = true;
420 } else if(selectedUrlString.startsWith("ssh")) { // NOI18N
421 repositoryPanel.tipLabel.setText(SSH_URL_HELP);
422 sshFields = true;
423 } else if(selectedUrlString.startsWith("file:")) { // NOI18N
424 repositoryPanel.tipLabel.setText(LOCAL_URL_HELP);
425 } else {
426 repositoryPanel.tipLabel.setText(NbBundle.getMessage(GitRepositoryUI.class, "MSG_Repository_Url_Help", new Object [] { // NOI18N
427 LOCAL_URL_HELP, HTTP_URL_HELP, HTTPS_URL_HELP, GIT_URL_HELP, GIT_SSH_URL_HELP, SSH_URL_HELP
428 }));
431 repositoryPanel.userPasswordField.setVisible(authFields);
432 repositoryPanel.passwordLabel.setVisible(authFields);
433 repositoryPanel.userTextField.setVisible(authFields);
434 repositoryPanel.leaveBlankLabel.setVisible(authFields);
435 repositoryPanel.userLabel.setVisible(authFields);
436 //repositoryPanel.savePasswordCheckBox.setVisible(authFields);
437 repositoryPanel.savePasswordCheckBox.setVisible(false);
438 repositoryPanel.proxySettingsButton.setVisible(proxyFields && ((modeMask & FLAG_SHOW_PROXY) != 0));
440 repositoryPanel.savePasswordCheckBox.setVisible(false);
441 repositoryPanel.tunnelCommandTextField.setVisible(false);
442 repositoryPanel.tunnelCommandLabel.setVisible(false);
443 repositoryPanel.tunnelLabel.setVisible(false);
444 repositoryPanel.tunnelHelpLabel.setVisible(false);
447 public void setEditable(boolean editable) {
448 assert EventQueue.isDispatchThread();
450 repositoryPanel.urlComboBox.setEnabled(editable && isSet(FLAG_URL_ENABLED));
451 repositoryPanel.userTextField.setEnabled(editable && valid);
452 repositoryPanel.userPasswordField.setEnabled(editable && valid);
453 repositoryPanel.savePasswordCheckBox.setEnabled(editable && valid);
454 repositoryPanel.tunnelCommandTextField.setEnabled(editable && valid);
455 repositoryPanel.proxySettingsButton.setEnabled(editable && valid);
459 * Load selected root from Swing structures (from arbitrary thread).
460 * @return null on failure
462 public String getUrlString() {
463 return urlComboEditor.getText().trim();
466 private String getUsername() {
467 return repositoryPanel.userTextField.getText().trim();
470 private String getPassword() {
471 char[] password = repositoryPanel.userPasswordField.getPassword();
472 String result = new String(password);
473 Arrays.fill(password, (char) 0);
474 return result;
477 private String getExternalCommand() {
478 return repositoryPanel.tunnelCommandTextField.getText();
481 private boolean isSavePassword() {
482 return repositoryPanel.savePasswordCheckBox.isSelected();
485 public URIish getUrl() throws URISyntaxException, MalformedURLException {
486 prepareUrl();
487 assert (url != null);
488 return url;
491 public Transport getRepositoryConnection() throws URISyntaxException {
492 prepareRepositoryConnection();
493 assert (repositoryConnection != null);
494 return repositoryConnection;
497 private void prepareUrl() throws URISyntaxException, MalformedURLException {
498 if (url != null) {
499 return;
502 String urlString = getUrlString();
503 String username = getUsername();
505 if (username.length() == 0) {
506 url = new URIish(urlString);
507 } else {
508 // Parse the URL, create a URI and convert back to URL just to
509 // add the user + password.
510 URL jurl = new URL(urlString);
512 String protocol = jurl.getProtocol();
513 String host = jurl.getHost();
514 int port = jurl.getPort();
515 String path = jurl.getPath();
516 String userInfo = username + ":" + getPassword();
518 URI juri = new URI(protocol, userInfo, host, port, path, null, null);
519 url = new URIish(juri.toURL());
523 private void prepareRepositoryConnection() {
524 if (repositoryConnection != null) {
525 return;
527 String extCommand = getExternalCommand();
528 boolean savePassword = isSavePassword();
530 //repositoryConnection = new Transport(url, extCommand, savePassword);
531 // FIXME: we need the local repository
534 private void onUsernameChange() {
535 repositoryConnection = null;
536 url = null;
539 private void onPasswordChange() {
540 repositoryConnection = null;
541 url = null;
544 private void onTunnelCommandChange() {
545 repositoryConnection = null;
548 private void onSavePasswordChange() {
549 repositoryConnection = null;
552 public RepositoryPanel getPanel() {
553 return repositoryPanel;
556 public boolean isValid() {
557 return valid;
560 private void setValid() {
561 setValid(true, ""); //NOI18N
564 public void setInvalid() {
565 setValid(false, "");
568 private void setValid(boolean valid, String message) {
569 if ((valid == this.valid) && message.equals(this.message)) {
570 return;
573 if (valid != this.valid) {
574 repositoryPanel.proxySettingsButton.setEnabled(valid);
575 repositoryPanel.userPasswordField.setEnabled(valid);
576 repositoryPanel.userTextField.setEnabled(valid);
577 //repositoryPanel.savePasswordCheckBox.setEnabled(valid);
580 this.valid = valid;
581 this.message = message;
583 fireStateChanged();
586 private void fireStateChanged() {
587 if ((listeners != null) && !listeners.isEmpty()) {
588 for (ChangeListener l : listeners) {
589 l.stateChanged(changeEvent);
594 public void addChangeListener(ChangeListener l) {
595 if(listeners==null) {
596 listeners = new ArrayList<ChangeListener>(4);
598 listeners.add(l);
601 public void removeChangeListener(ChangeListener l) {
602 if(listeners==null) {
603 return;
605 listeners.remove(l);
608 public String getMessage() {
609 return message;
612 public void focusGained(FocusEvent focusEvent) {
613 if(focusEvent.getSource()==repositoryPanel.userPasswordField) {
614 repositoryPanel.userPasswordField.selectAll();
618 public void focusLost(FocusEvent focusEvent) {
619 // do nothing
622 public void itemStateChanged(ItemEvent evt) {
623 Object source = evt.getSource();
625 if (source == repositoryPanel.urlComboBox) {
626 if(evt.getStateChange() == ItemEvent.SELECTED) {
627 comboBoxItemSelected(evt.getItem());
629 } else if (source == repositoryPanel.savePasswordCheckBox) {
630 onSavePasswordChange();
631 } else {
632 assert false;
636 private void comboBoxItemSelected(Object selectedItem) {
637 if (selectedItem.getClass() == String.class) {
638 urlPrefixSelected();
639 } else if (selectedItem instanceof Transport) {
640 repositoryConnectionSelected((Transport) selectedItem);
641 } else {
642 assert false;
646 private void urlPrefixSelected() {
647 repositoryPanel.userTextField.setText(null);
648 repositoryPanel.userPasswordField.setText(null);
649 repositoryPanel.tunnelCommandTextField.setText(null);
650 repositoryPanel.savePasswordCheckBox.setSelected(false);
652 url = null;
653 repositoryConnection = null;
656 private void repositoryConnectionSelected(Transport rc) {
657 url = rc.getURI();
658 repositoryPanel.userTextField.setText(url.getUser());
659 repositoryPanel.userPasswordField.setText(url.getPass());
661 repositoryConnection = rc;
664 public void setTipVisible(Boolean flag) {
665 repositoryPanel.tipLabel.setVisible(flag);
668 public boolean show(String title, HelpCtx helpCtx, boolean setMaxNeddedSize) {
669 RepositoryDialogPanel corectPanel = new RepositoryDialogPanel();
670 corectPanel.panel.setLayout(new BorderLayout());
671 JPanel p = getPanel();
672 if(setMaxNeddedSize) {
673 if(bPushPull){
674 maxNeededSize.setSize(maxNeededSize.width, maxNeededSize.height + GIT_PUSH_PULL_VERT_PADDING);
676 p.setPreferredSize(maxNeededSize);
678 corectPanel.panel.add(p, BorderLayout.NORTH);
679 DialogDescriptor dialogDescriptor = new DialogDescriptor(corectPanel, title); // NOI18N
680 showDialog(dialogDescriptor, helpCtx, null);
681 return dialogDescriptor.getValue() == DialogDescriptor.OK_OPTION;
684 public Object show(String title, HelpCtx helpCtx, Object[] options, boolean setMaxNeededSize, String name) {
685 RepositoryDialogPanel corectPanel = new RepositoryDialogPanel();
686 corectPanel.panel.setLayout(new BorderLayout());
687 corectPanel.panel.add(getPanel(), BorderLayout.NORTH);
688 DialogDescriptor dialogDescriptor = new DialogDescriptor(corectPanel, title); // NOI18N
689 JPanel p = getPanel();
690 if(setMaxNeededSize) {
691 if(bPushPull){
692 maxNeededSize.setSize(maxNeededSize.width, maxNeededSize.height + GIT_PUSH_PULL_VERT_PADDING);
694 p.setPreferredSize(maxNeededSize);
696 if(options!= null) {
697 dialogDescriptor.setOptions(options); // NOI18N
699 showDialog(dialogDescriptor, helpCtx, name);
700 return dialogDescriptor.getValue();
703 private void showDialog(DialogDescriptor dialogDescriptor, HelpCtx helpCtx, String name) {
704 dialogDescriptor.setModal(true);
705 dialogDescriptor.setHelpCtx(helpCtx);
707 Dialog dialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
708 if (name != null) {
709 dialog.addWindowListener(new DialogBoundsPreserver(GitModuleConfig.getDefault().getPreferences(), name)); // NOI18N
711 dialog.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(GitRepositoryUI.class, "ACSD_RepositoryPanel"));
713 dialog.setVisible(true);
716 private boolean isSet(int flag) {
717 return (modeMask & flag) != 0;