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]"
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
;
59 import java
.net
.URISyntaxException
;
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
;
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
;
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
;
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
144 updateVisibility("foo:"); // NOI18N
146 updateVisibility("https:"); // NOI18N
147 maxNeededSize
= repositoryPanel
.getPreferredSize();
149 //TODO: implement this
150 //repositoryPanel.savePasswordCheckBox.setSelected(GitModuleConfig.getDefault().getSavePassword());
151 repositoryPanel
.schedulePostInitRoutine(new Runnable() {
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 */
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:
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()}
223 private final class UrlComboBoxEditor
implements ComboBoxEditor
,
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;
238 setItemImpl(anObject
);
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());
253 origEditor
.setItem(anObject
);
257 public Component
getEditorComponent() {
258 return origEditor
.getEditorComponent();
260 public Object
getItem() {
261 Transport repoConn
= getRepoConn();
262 if (repoConn
!= null) {
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
) {
281 public void removeUpdate(DocumentEvent e
) {
284 public void changedUpdate(DocumentEvent e
) {
287 private void textChanged() {
288 if (urlBeingSelectedFromPopup
) {
294 private Transport
getRepoConn() {
295 if (repoConnRef
!= null) {
296 Transport repoConn
= repoConnRef
.get();
297 if (repoConn
!= null) {
304 private void clearRepoConnRef() {
305 if (repoConnRef
!= null) {
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
));
336 private static String
createURIPrefixForScheme(GitURIScheme scheme
) {
337 if (scheme
== GitURIScheme
.FILE
) {
338 return scheme
+ ":/"; //NOI18N
340 return scheme
+ "://"; //NOI18N
344 private final class DocumentChangeHandler
implements DocumentListener
{
346 DocumentChangeHandler() { }
348 public void insertUpdate(DocumentEvent e
) {
351 public void removeUpdate(DocumentEvent e
) {
354 public void changedUpdate(DocumentEvent 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
) {
368 } else if (modifiedDocument
== usernameDoc
) {
370 } else if (modifiedDocument
== passwordDoc
) {
372 } else if (modifiedDocument
== tunnelCmdDoc
) {
373 onTunnelCommandChange();
380 * Always updates UI fields visibility.
382 private void onUrlChange() {
383 if (!urlBeingSelectedFromPopup
) {
384 repositoryConnection
= 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();
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
);
410 } else if(selectedUrlString
.startsWith("https:")) { // NOI18N
411 repositoryPanel
.tipLabel
.setText(HTTPS_URL_HELP
);
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
);
420 } else if(selectedUrlString
.startsWith("ssh")) { // NOI18N
421 repositoryPanel
.tipLabel
.setText(SSH_URL_HELP
);
423 } else if(selectedUrlString
.startsWith("file:")) { // NOI18N
424 repositoryPanel
.tipLabel
.setText(LOCAL_URL_HELP
);
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
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);
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
{
487 assert (url
!= null);
491 public Transport
getRepositoryConnection() throws URISyntaxException
{
492 prepareRepositoryConnection();
493 assert (repositoryConnection
!= null);
494 return repositoryConnection
;
497 private void prepareUrl() throws URISyntaxException
, MalformedURLException
{
502 String urlString
= getUrlString();
503 String username
= getUsername();
505 if (username
.length() == 0) {
506 url
= new URIish(urlString
);
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) {
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;
539 private void onPasswordChange() {
540 repositoryConnection
= 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() {
560 private void setValid() {
561 setValid(true, ""); //NOI18N
564 public void setInvalid() {
568 private void setValid(boolean valid
, String message
) {
569 if ((valid
== this.valid
) && message
.equals(this.message
)) {
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);
581 this.message
= message
;
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);
601 public void removeChangeListener(ChangeListener l
) {
602 if(listeners
==null) {
608 public String
getMessage() {
612 public void focusGained(FocusEvent focusEvent
) {
613 if(focusEvent
.getSource()==repositoryPanel
.userPasswordField
) {
614 repositoryPanel
.userPasswordField
.selectAll();
618 public void focusLost(FocusEvent focusEvent
) {
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();
636 private void comboBoxItemSelected(Object selectedItem
) {
637 if (selectedItem
.getClass() == String
.class) {
639 } else if (selectedItem
instanceof Transport
) {
640 repositoryConnectionSelected((Transport
) selectedItem
);
646 private void urlPrefixSelected() {
647 repositoryPanel
.userTextField
.setText(null);
648 repositoryPanel
.userPasswordField
.setText(null);
649 repositoryPanel
.tunnelCommandTextField
.setText(null);
650 repositoryPanel
.savePasswordCheckBox
.setSelected(false);
653 repositoryConnection
= null;
656 private void repositoryConnectionSelected(Transport rc
) {
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
) {
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
) {
692 maxNeededSize
.setSize(maxNeededSize
.width
, maxNeededSize
.height
+ GIT_PUSH_PULL_VERT_PADDING
);
694 p
.setPreferredSize(maxNeededSize
);
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
);
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;