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