4161e903eadd2f7c2c0a32dbada97552e471d77d
[idea/community.git] / plugins / git4idea / src / git4idea / push / GitPushTargetPanel.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package git4idea.push;
17
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;
38
39 import javax.swing.*;
40 import javax.swing.tree.DefaultMutableTreeNode;
41 import java.awt.*;
42 import java.awt.event.MouseEvent;
43 import java.text.ParseException;
44 import java.util.Comparator;
45 import java.util.List;
46
47 public class GitPushTargetPanel extends PushTargetPanel<GitPushTarget> {
48
49   private static final Logger LOG = Logger.getInstance(GitPushTargetPanel.class);
50
51   private static final Comparator<GitRemoteBranch> REMOTE_BRANCH_COMPARATOR = new MyRemoteBranchComparator();
52   private static final String SEPARATOR = " : ";
53
54   @NotNull private final GitRepository myRepository;
55   @NotNull private final VcsEditableTextComponent myTargetRenderer;
56   @NotNull private final PushTargetTextField myTargetEditor;
57   @NotNull private final VcsLinkedTextComponent myRemoteRenderer;
58
59   @Nullable private GitPushTarget myCurrentTarget;
60   @Nullable private String myError;
61   @Nullable private Runnable myFireOnChangeAction;
62
63   public GitPushTargetPanel(@NotNull GitRepository repository, @Nullable GitPushTarget defaultTarget) {
64     myRepository = repository;
65
66     myCurrentTarget = defaultTarget;
67
68     String initialBranch = "";
69     String initialRemote = "";
70     if (defaultTarget == null) {
71       if (repository.getCurrentBranch() == null) {
72         myError = "Detached HEAD";
73       }
74       else if (repository.getRemotes().isEmpty()) {
75         myError = "No remotes";
76       }
77       else if (repository.isFresh()) {
78         myError = "Empty repository";
79       }
80       else {
81         myError = "Can't push";
82       }
83     }
84     else {
85       initialBranch = getTextFieldText(defaultTarget);
86       initialRemote = defaultTarget.getBranch().getRemote().getName();
87     }
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() {
91       @Override
92       public void hyperlinkActivated(@NotNull DefaultMutableTreeNode sourceNode, @NotNull MouseEvent event) {
93         showRemoteSelector(event);
94       }
95     });
96
97     setLayout(new BorderLayout());
98     setOpaque(false);
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);
103
104     add(remoteAndSeparator, BorderLayout.WEST);
105     add(myTargetEditor, BorderLayout.CENTER);
106     updateTextField();
107   }
108
109   private void updateTextField() {
110     myTargetEditor.setVisible(!myRepository.getRemotes().isEmpty());
111   }
112
113   private void showRemoteSelector(@NotNull MouseEvent event) {
114     final List<String> remotes = getRemotes();
115     if (remotes.size() <= 1) {
116       return;
117     }
118
119     ListPopup popup = JBPopupFactory.getInstance().createListPopup(new BaseListPopupStep<String>(null, remotes) {
120       @Override
121       public PopupStep onChosen(String selectedValue, boolean finalChoice) {
122         myRemoteRenderer.updateLinkText(selectedValue);
123         if (myFireOnChangeAction != null) {
124           myFireOnChangeAction.run();
125         }
126         return super.onChosen(selectedValue, finalChoice);
127       }
128     });
129     popup.show(new RelativePoint(event));
130   }
131
132   @NotNull
133   private List<String> getRemotes() {
134     return ContainerUtil.map(myRepository.getRemotes(), new Function<GitRemote, String>() {
135       @Override
136       public String fun(GitRemote remote) {
137         return remote.getName();
138       }
139     });
140   }
141
142   @Override
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));
147     }
148     else {
149       String currentRemote = myRemoteRenderer.getText();
150       if (getRemotes().size() > 1) {
151         myRemoteRenderer.setSelected(isSelected);
152         myRemoteRenderer.setTransparent(!isActive);
153         myRemoteRenderer.render(renderer);
154       }
155       else {
156         renderer.append(currentRemote, targetTextAttributes);
157       }
158       renderer.append(SEPARATOR, targetTextAttributes);
159
160       GitPushTarget target = getValue();
161       if (target.isNewBranchCreated()) {
162         renderer.append("+", PushLogTreeUtil.addTransparencyIfNeeded(SimpleTextAttributes.SYNTHETIC_ATTRIBUTES, isActive), this);
163       }
164       myTargetRenderer.setSelected(isSelected);
165       myTargetRenderer.setTransparent(!isActive);
166       myTargetRenderer.render(renderer);
167     }
168   }
169
170   @NotNull
171   @Override
172   public GitPushTarget getValue() {
173     return ObjectUtils.assertNotNull(myCurrentTarget);
174   }
175
176   @NotNull
177   private static String getTextFieldText(@Nullable GitPushTarget target) {
178     return (target != null ? target.getBranch().getNameForRemoteOperations() : "");
179   }
180
181   @Override
182   public void fireOnCancel() {
183     myTargetEditor.setText(getTextFieldText(myCurrentTarget));
184   }
185
186   @Override
187   public void fireOnChange() {
188     if (myError != null) {
189       return;
190     }
191     String remoteName = myRemoteRenderer.getText();
192     String branchName = myTargetEditor.getText();
193     try {
194       myCurrentTarget = GitPushTarget.parse(myRepository, remoteName, branchName);
195       myTargetRenderer.updateLinkText(branchName);
196     }
197     catch (ParseException e) {
198       LOG.error("Invalid remote name shouldn't be allowed. [" + remoteName + ", " + branchName + "]", e);
199     }
200   }
201
202   @Nullable
203   @Override
204   public ValidationInfo verify() {
205     if (myError != null) {
206       return new ValidationInfo(myError, myTargetEditor);
207     }
208     try {
209       GitPushTarget.parse(myRepository, myRemoteRenderer.getText(), myTargetEditor.getText());
210       return null;
211     }
212     catch (ParseException e) {
213       return new ValidationInfo(e.getMessage(), myTargetEditor);
214     }
215   }
216
217   @SuppressWarnings("NullableProblems")
218   @Override
219   public void setFireOnChangeAction(@NotNull Runnable action) {
220     myFireOnChangeAction = action;
221   }
222
223   @NotNull
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>() {
227       @Override
228       public String fun(GitRemoteBranch branch) {
229         return branch.getNameForRemoteOperations();
230       }
231     });
232   }
233
234   private static class MyRemoteBranchComparator implements Comparator<GitRemoteBranch> {
235     @Override
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)) {
242           return -1;
243         }
244         if (remoteName2.equals(GitRemote.ORIGIN_NAME)) {
245           return 1;
246         }
247         return remoteComparison;
248       }
249       return o1.getNameForLocalOperations().compareTo(o2.getNameForLocalOperations());
250     }
251   }
252 }