2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package git4idea.push;
18 import com.intellij.dvcs.push.PushTargetPanel;
19 import com.intellij.dvcs.push.ui.*;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.ui.ValidationInfo;
22 import com.intellij.openapi.ui.popup.JBPopupFactory;
23 import com.intellij.openapi.ui.popup.ListPopup;
24 import com.intellij.openapi.ui.popup.PopupStep;
25 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
26 import com.intellij.ui.ColoredTreeCellRenderer;
27 import com.intellij.ui.SimpleTextAttributes;
28 import com.intellij.ui.awt.RelativePoint;
29 import com.intellij.ui.components.JBLabel;
30 import com.intellij.util.Function;
31 import com.intellij.util.ObjectUtils;
32 import com.intellij.util.containers.ContainerUtil;
33 import git4idea.GitRemoteBranch;
34 import git4idea.repo.GitRemote;
35 import git4idea.repo.GitRepository;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
40 import javax.swing.tree.DefaultMutableTreeNode;
42 import java.awt.event.MouseEvent;
43 import java.text.ParseException;
44 import java.util.Comparator;
45 import java.util.List;
47 public class GitPushTargetPanel extends PushTargetPanel<GitPushTarget> {
49 private static final Logger LOG = Logger.getInstance(GitPushTargetPanel.class);
51 private static final Comparator<GitRemoteBranch> REMOTE_BRANCH_COMPARATOR = new MyRemoteBranchComparator();
52 private static final String SEPARATOR = " : ";
54 @NotNull private final GitRepository myRepository;
55 @NotNull private final VcsEditableTextComponent myTargetRenderer;
56 @NotNull private final PushTargetTextField myTargetEditor;
57 @NotNull private final VcsLinkedTextComponent myRemoteRenderer;
59 @Nullable private GitPushTarget myCurrentTarget;
60 @Nullable private String myError;
61 @Nullable private Runnable myFireOnChangeAction;
63 public GitPushTargetPanel(@NotNull GitRepository repository, @Nullable GitPushTarget defaultTarget) {
64 myRepository = repository;
66 myCurrentTarget = defaultTarget;
68 String initialBranch = "";
69 String initialRemote = "";
70 if (defaultTarget == null) {
71 if (repository.getCurrentBranch() == null) {
72 myError = "Detached HEAD";
74 else if (repository.getRemotes().isEmpty()) {
75 myError = "No remotes";
77 else if (repository.isFresh()) {
78 myError = "Empty repository";
81 myError = "Can't push";
85 initialBranch = getTextFieldText(defaultTarget);
86 initialRemote = defaultTarget.getBranch().getRemote().getName();
88 myTargetRenderer = new VcsEditableTextComponent("<a href=''>" + initialBranch + "</a>", null);
89 myTargetEditor = new PushTargetTextField(repository.getProject(), getTargetNames(myRepository), initialBranch);
90 myRemoteRenderer = new VcsLinkedTextComponent("<a href=''>" + initialRemote + "</a>", new VcsLinkListener() {
92 public void hyperlinkActivated(@NotNull DefaultMutableTreeNode sourceNode, @NotNull MouseEvent event) {
93 showRemoteSelector(event);
97 setLayout(new BorderLayout());
99 JPanel remoteAndSeparator = new JPanel(new BorderLayout());
100 remoteAndSeparator.setOpaque(false);
101 remoteAndSeparator.add(myRemoteRenderer, BorderLayout.CENTER);
102 remoteAndSeparator.add(new JBLabel(SEPARATOR), BorderLayout.EAST);
104 add(remoteAndSeparator, BorderLayout.WEST);
105 add(myTargetEditor, BorderLayout.CENTER);
109 private void updateTextField() {
110 myTargetEditor.setVisible(!myRepository.getRemotes().isEmpty());
113 private void showRemoteSelector(@NotNull MouseEvent event) {
114 final List<String> remotes = getRemotes();
115 if (remotes.size() <= 1) {
119 ListPopup popup = JBPopupFactory.getInstance().createListPopup(new BaseListPopupStep<String>(null, remotes) {
121 public PopupStep onChosen(String selectedValue, boolean finalChoice) {
122 myRemoteRenderer.updateLinkText(selectedValue);
123 if (myFireOnChangeAction != null) {
124 myFireOnChangeAction.run();
126 return super.onChosen(selectedValue, finalChoice);
129 popup.show(new RelativePoint(event));
133 private List<String> getRemotes() {
134 return ContainerUtil.map(myRepository.getRemotes(), new Function<GitRemote, String>() {
136 public String fun(GitRemote remote) {
137 return remote.getName();
143 public void render(@NotNull ColoredTreeCellRenderer renderer, boolean isSelected, boolean isActive) {
144 SimpleTextAttributes targetTextAttributes = PushLogTreeUtil.addTransparencyIfNeeded(SimpleTextAttributes.REGULAR_ATTRIBUTES, isActive);
145 if (myError != null) {
146 renderer.append(myError, PushLogTreeUtil.addTransparencyIfNeeded(SimpleTextAttributes.ERROR_ATTRIBUTES, isActive));
149 String currentRemote = myRemoteRenderer.getText();
150 if (getRemotes().size() > 1) {
151 myRemoteRenderer.setSelected(isSelected);
152 myRemoteRenderer.setTransparent(!isActive);
153 myRemoteRenderer.render(renderer);
156 renderer.append(currentRemote, targetTextAttributes);
158 renderer.append(SEPARATOR, targetTextAttributes);
160 GitPushTarget target = getValue();
161 if (target.isNewBranchCreated()) {
162 renderer.append("+", PushLogTreeUtil.addTransparencyIfNeeded(SimpleTextAttributes.SYNTHETIC_ATTRIBUTES, isActive), this);
164 myTargetRenderer.setSelected(isSelected);
165 myTargetRenderer.setTransparent(!isActive);
166 myTargetRenderer.render(renderer);
172 public GitPushTarget getValue() {
173 return ObjectUtils.assertNotNull(myCurrentTarget);
177 private static String getTextFieldText(@Nullable GitPushTarget target) {
178 return (target != null ? target.getBranch().getNameForRemoteOperations() : "");
182 public void fireOnCancel() {
183 myTargetEditor.setText(getTextFieldText(myCurrentTarget));
187 public void fireOnChange() {
188 if (myError != null) {
191 String remoteName = myRemoteRenderer.getText();
192 String branchName = myTargetEditor.getText();
194 myCurrentTarget = GitPushTarget.parse(myRepository, remoteName, branchName);
195 myTargetRenderer.updateLinkText(branchName);
197 catch (ParseException e) {
198 LOG.error("Invalid remote name shouldn't be allowed. [" + remoteName + ", " + branchName + "]", e);
204 public ValidationInfo verify() {
205 if (myError != null) {
206 return new ValidationInfo(myError, myTargetEditor);
209 GitPushTarget.parse(myRepository, myRemoteRenderer.getText(), myTargetEditor.getText());
212 catch (ParseException e) {
213 return new ValidationInfo(e.getMessage(), myTargetEditor);
217 @SuppressWarnings("NullableProblems")
219 public void setFireOnChangeAction(@NotNull Runnable action) {
220 myFireOnChangeAction = action;
224 public static List<String> getTargetNames(@NotNull GitRepository repository) {
225 List<GitRemoteBranch> remoteBranches = ContainerUtil.sorted(repository.getBranches().getRemoteBranches(), REMOTE_BRANCH_COMPARATOR);
226 return ContainerUtil.map(remoteBranches, new Function<GitRemoteBranch, String>() {
228 public String fun(GitRemoteBranch branch) {
229 return branch.getNameForRemoteOperations();
234 private static class MyRemoteBranchComparator implements Comparator<GitRemoteBranch> {
236 public int compare(@NotNull GitRemoteBranch o1, @NotNull GitRemoteBranch o2) {
237 String remoteName1 = o1.getRemote().getName();
238 String remoteName2 = o2.getRemote().getName();
239 int remoteComparison = remoteName1.compareTo(remoteName2);
240 if (remoteComparison != 0) {
241 if (remoteName1.equals(GitRemote.ORIGIN_NAME)) {
244 if (remoteName2.equals(GitRemote.ORIGIN_NAME)) {
247 return remoteComparison;
249 return o1.getNameForLocalOperations().compareTo(o2.getNameForLocalOperations());