d829395c5dc3aadbea0848ffee79d21745c4e637
[idea/community.git] / platform / vcs-log / impl / src / com / intellij / vcs / log / ui / frame / DetailsPanel.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 com.intellij.vcs.log.ui.frame;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.progress.util.ProgressWindow;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.Condition;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkHtmlRenderer;
24 import com.intellij.openapi.vfs.VirtualFile;
25 import com.intellij.ui.BrowserHyperlinkListener;
26 import com.intellij.ui.IdeBorderFactory;
27 import com.intellij.ui.ScrollPaneFactory;
28 import com.intellij.ui.components.JBLabel;
29 import com.intellij.ui.components.JBLoadingPanel;
30 import com.intellij.ui.components.JBTextField;
31 import com.intellij.ui.components.panels.NonOpaquePanel;
32 import com.intellij.util.containers.ContainerUtil;
33 import com.intellij.util.text.DateFormatUtil;
34 import com.intellij.util.ui.AsyncProcessIcon;
35 import com.intellij.util.ui.UIUtil;
36 import com.intellij.vcs.log.Hash;
37 import com.intellij.vcs.log.VcsFullCommitDetails;
38 import com.intellij.vcs.log.VcsRef;
39 import com.intellij.vcs.log.data.LoadingDetails;
40 import com.intellij.vcs.log.data.VcsLogDataHolder;
41 import com.intellij.vcs.log.data.VisiblePack;
42 import com.intellij.vcs.log.printer.idea.PrintParameters;
43 import com.intellij.vcs.log.ui.VcsLogColorManager;
44 import com.intellij.vcs.log.ui.render.RefPainter;
45 import com.intellij.vcs.log.ui.tables.GraphTableModel;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
48
49 import javax.swing.*;
50 import javax.swing.border.LineBorder;
51 import javax.swing.event.ListSelectionEvent;
52 import javax.swing.event.ListSelectionListener;
53 import java.awt.*;
54 import java.util.Collection;
55 import java.util.Collections;
56 import java.util.List;
57
58 /**
59  * @author Kirill Likhodedov
60  */
61 class DetailsPanel extends JPanel implements ListSelectionListener {
62
63   private static final Logger LOG = Logger.getInstance("Vcs.Log");
64
65   private static final String STANDARD_LAYER = "Standard";
66   private static final String MESSAGE_LAYER = "Message";
67
68   @NotNull private final VcsLogDataHolder myLogDataHolder;
69   @NotNull private final VcsLogGraphTable myGraphTable;
70
71   @NotNull private final RefsPanel myRefsPanel;
72   @NotNull private final DataPanel myHashAuthorPanel;
73   @NotNull private final DataPanel myMessageDataPanel;
74   @NotNull private final ContainingBranchesPanel myContainingBranchesPanel;
75   @NotNull private final MessagePanel myMessagePanel;
76   @NotNull private final JBLoadingPanel myLoadingPanel;
77
78   @NotNull private VisiblePack myDataPack;
79
80   DetailsPanel(@NotNull VcsLogDataHolder logDataHolder, @NotNull VcsLogGraphTable graphTable, @NotNull VcsLogColorManager colorManager,
81                @NotNull VisiblePack initialDataPack) {
82     myLogDataHolder = logDataHolder;
83     myGraphTable = graphTable;
84     myDataPack = initialDataPack;
85
86     myRefsPanel = new RefsPanel(colorManager);
87     myHashAuthorPanel = new DataPanel(logDataHolder.getProject(), false);
88
89     final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane();
90     myMessageDataPanel = new DataPanel(logDataHolder.getProject(), true) {
91       @Override
92       public Dimension getPreferredSize() {
93         Dimension size = super.getPreferredSize();
94         size.width = scrollPane.getViewport().getWidth() - 5;
95         return size;
96       }
97     };
98     scrollPane.setViewportView(myMessageDataPanel);
99     scrollPane.setBorder(UIUtil.makeChameleonBorder(scrollPane.getBorder(), new Condition<JScrollBar>() {
100       @Override
101       public boolean value(JScrollBar bar) {
102         return bar != null && bar.isVisible();
103       }
104     }, scrollPane.getVerticalScrollBar()));
105
106     myContainingBranchesPanel = new ContainingBranchesPanel();
107     myMessagePanel = new MessagePanel();
108
109     myLoadingPanel = new JBLoadingPanel(new BorderLayout(), logDataHolder, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS);
110     JPanel header = new NonOpaquePanel(new BorderLayout());
111     header.add(myRefsPanel, BorderLayout.NORTH);
112     header.add(myHashAuthorPanel, BorderLayout.SOUTH);
113     myLoadingPanel.add(header, BorderLayout.NORTH);
114     myLoadingPanel.add(scrollPane, BorderLayout.CENTER);
115     myLoadingPanel.add(myContainingBranchesPanel, BorderLayout.SOUTH);
116     myLoadingPanel.setBackground(UIUtil.getTableBackground());
117
118     setLayout(new CardLayout());
119     add(myLoadingPanel, STANDARD_LAYER);
120     add(myMessagePanel, MESSAGE_LAYER);
121
122     setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
123     setBackground(UIUtil.getTableBackground());
124     showMessage("No commits selected");
125   }
126
127   void updateDataPack(@NotNull VisiblePack dataPack) {
128     myDataPack = dataPack;
129   }
130
131   @Override
132   public void valueChanged(@Nullable ListSelectionEvent notUsed) {
133     int[] rows = myGraphTable.getSelectedRows();
134     if (rows.length < 1) {
135       showMessage("No commits selected");
136     }
137     else if (rows.length > 1) {
138       showMessage("Several commits selected");
139     }
140     else {
141       ((CardLayout)getLayout()).show(this, STANDARD_LAYER);
142       int row = rows[0];
143       GraphTableModel tableModel = (GraphTableModel)myGraphTable.getModel();
144       Hash hash = tableModel.getHashAtRow(row);
145       VcsFullCommitDetails commitData = myLogDataHolder.getCommitDetailsGetter().getCommitData(row, tableModel);
146       if (commitData == null || hash == null) {
147         showMessage("No commits selected");
148         return;
149       }
150       if (commitData instanceof LoadingDetails) {
151         myLoadingPanel.startLoading();
152         myHashAuthorPanel.setData(null);
153         myMessageDataPanel.setData(null);
154         myRefsPanel.setRefs(Collections.<VcsRef>emptyList());
155       }
156       else {
157         myLoadingPanel.stopLoading();
158         myHashAuthorPanel.setData(commitData);
159         myMessageDataPanel.setData(commitData);
160         myRefsPanel.setRefs(sortRefs(hash, commitData.getRoot()));
161       }
162
163       List<String> branches = null;
164       if (!(commitData instanceof LoadingDetails)) {
165         branches = myLogDataHolder.getContainingBranchesGetter().requestContainingBranches(commitData.getRoot(), hash);
166       }
167       myContainingBranchesPanel.setBranches(branches);
168     }
169   }
170
171   private void showMessage(String text) {
172     myLoadingPanel.stopLoading();
173     ((CardLayout)getLayout()).show(this, MESSAGE_LAYER);
174     myMessagePanel.setText(text);
175   }
176
177   @NotNull
178   private List<VcsRef> sortRefs(@NotNull Hash hash, @NotNull VirtualFile root) {
179     Collection<VcsRef> refs = myDataPack.getRefsModel().refsToCommit(hash);
180     return ContainerUtil.sorted(refs, myLogDataHolder.getLogProvider(root).getReferenceManager().getComparator());
181   }
182
183   private static class DataPanel extends JEditorPane {
184
185     @NotNull private final Project myProject;
186     private final boolean myMessageMode;
187
188     DataPanel(@NotNull Project project, boolean messageMode) {
189       super(UIUtil.HTML_MIME, "");
190       myMessageMode = messageMode;
191       setEditable(false);
192       myProject = project;
193       addHyperlinkListener(BrowserHyperlinkListener.INSTANCE);
194       setOpaque(false);
195     }
196
197     void setData(@Nullable VcsFullCommitDetails commit) {
198       if (commit == null) {
199         setText("");
200       }
201       else {
202         String body;
203         if (myMessageMode) {
204           body = getMessageText(commit);
205         } else {
206           body = commit.getId().asString() + "<br/>" + getAuthorText(commit);
207         }
208         setText("<html><head>" + UIUtil.getCssFontDeclaration(UIUtil.getLabelFont()) + "</head><body>" + body + "</body></html>");
209         setCaretPosition(0);
210       }
211       revalidate();
212       repaint();
213     }
214
215     @Override
216     public Dimension getPreferredSize() {
217       Dimension size = super.getPreferredSize();
218       int h = getFontMetrics(getFont()).getHeight();
219       size.height = Math.max(size.height, myMessageMode ? 5 * h : 2 * h);
220       return size;
221     }
222
223     private String getMessageText(VcsFullCommitDetails commit) {
224       String fullMessage = commit.getFullMessage();
225       int separator = fullMessage.indexOf("\n\n");
226       String subject = separator > 0 ? fullMessage.substring(0, separator) : fullMessage;
227       String description = fullMessage.substring(subject.length());
228       return "<b>" + IssueLinkHtmlRenderer.formatTextWithLinks(myProject, subject) + "</b>" +
229              IssueLinkHtmlRenderer.formatTextWithLinks(myProject, description);
230     }
231
232     private static String getAuthorText(VcsFullCommitDetails commit) {
233       String authorText = commit.getAuthor().getName() + " at " + DateFormatUtil.formatDateTime(commit.getAuthorTime());
234       if (!commit.getAuthor().equals(commit.getCommitter())) {
235         String commitTime;
236         if (commit.getAuthorTime() != commit.getTimestamp()) {
237           commitTime = " at " + DateFormatUtil.formatDateTime(commit.getTimestamp());
238         }
239         else {
240           commitTime = "";
241         }
242         authorText += " (committed by " + commit.getCommitter().getName() + commitTime + ")";
243       }
244       else if (commit.getAuthorTime() != commit.getTimestamp()) {
245         authorText += " (committed at " + DateFormatUtil.formatDateTime(commit.getTimestamp()) + ")";
246       }
247       return authorText;
248     }
249   }
250
251   private static class ContainingBranchesPanel extends JPanel {
252
253     private final JComponent myLoadingComponent;
254     private final JTextField myBranchesList;
255
256     ContainingBranchesPanel() {
257       JLabel label = new JBLabel("Contained in branches: ") {
258         @Override
259         public Font getFont() {
260           return UIUtil.getLabelFont().deriveFont(Font.ITALIC);
261         }
262       };
263       myLoadingComponent = new NonOpaquePanel(new BorderLayout());
264       myLoadingComponent.add(new AsyncProcessIcon("Loading..."), BorderLayout.WEST);
265       myLoadingComponent.add(Box.createHorizontalGlue(), BorderLayout.CENTER);
266       myBranchesList = new JBTextField("");
267       myBranchesList.setEditable(false);
268       myBranchesList.setBorder(IdeBorderFactory.createEmptyBorder()); // setting border to null may mean "use default border" while setting empty border means we do not want a border
269       if (UIUtil.isUnderIntelliJLaF()) {
270         myBranchesList.setBackground(UIUtil.getPanelBackground());
271       } else if (UIUtil.isUnderGTKLookAndFeel()) {
272         // setting border to empty does not help with gtk l&f
273         // so I just cover it completely with my line border
274         myBranchesList.setBorder(new LineBorder(UIUtil.getTextFieldBackground(), 3));
275       }
276
277       setOpaque(false);
278       setLayout(new BorderLayout());
279       add(label, BorderLayout.WEST);
280       add(Box.createHorizontalGlue(), BorderLayout.EAST);
281     }
282
283     void setBranches(@Nullable List<String> branches) {
284       if (branches == null) {
285         remove(myBranchesList);
286         add(myLoadingComponent, BorderLayout.CENTER);
287       }
288       else {
289         remove(myLoadingComponent);
290         myBranchesList.setText(StringUtil.join(branches, ", "));
291         add(myBranchesList, BorderLayout.CENTER);
292       }
293       revalidate();
294       repaint();
295     }
296   }
297
298   private static class RefsPanel extends JPanel {
299
300     @NotNull private final RefPainter myRefPainter;
301     @NotNull private List<VcsRef> myRefs;
302
303     RefsPanel(@NotNull VcsLogColorManager colorManager) {
304       myRefPainter = new RefPainter(colorManager, false);
305       myRefs = Collections.emptyList();
306       setPreferredSize(new Dimension(-1, PrintParameters.HEIGHT_CELL + UIUtil.DEFAULT_VGAP));
307       setOpaque(false);
308     }
309
310     @Override
311     protected void paintComponent(Graphics g) {
312       // TODO when the right margin reaches, draw on the second line
313       myRefPainter.draw((Graphics2D)g, myRefs, 0, getWidth());
314     }
315
316     void setRefs(@NotNull List<VcsRef> refs) {
317       myRefs = refs;
318       setVisible(!myRefs.isEmpty());
319       repaint();
320     }
321   }
322
323   private static class MessagePanel extends NonOpaquePanel {
324
325     private final JLabel myLabel;
326
327     MessagePanel() {
328       super(new BorderLayout());
329       myLabel = new JLabel();
330       myLabel.setForeground(UIUtil.getInactiveTextColor());
331       myLabel.setHorizontalAlignment(SwingConstants.CENTER);
332       myLabel.setVerticalAlignment(SwingConstants.CENTER);
333       add(myLabel);
334     }
335
336     void setText(String text) {
337       myLabel.setText(text);
338     }
339   }
340 }