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 com.intellij.vcs.log.ui.frame;
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;
50 import javax.swing.border.LineBorder;
51 import javax.swing.event.ListSelectionEvent;
52 import javax.swing.event.ListSelectionListener;
54 import java.util.Collection;
55 import java.util.Collections;
56 import java.util.List;
59 * @author Kirill Likhodedov
61 class DetailsPanel extends JPanel implements ListSelectionListener {
63 private static final Logger LOG = Logger.getInstance("Vcs.Log");
65 private static final String STANDARD_LAYER = "Standard";
66 private static final String MESSAGE_LAYER = "Message";
68 @NotNull private final VcsLogDataHolder myLogDataHolder;
69 @NotNull private final VcsLogGraphTable myGraphTable;
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;
78 @NotNull private VisiblePack myDataPack;
80 DetailsPanel(@NotNull VcsLogDataHolder logDataHolder, @NotNull VcsLogGraphTable graphTable, @NotNull VcsLogColorManager colorManager,
81 @NotNull VisiblePack initialDataPack) {
82 myLogDataHolder = logDataHolder;
83 myGraphTable = graphTable;
84 myDataPack = initialDataPack;
86 myRefsPanel = new RefsPanel(colorManager);
87 myHashAuthorPanel = new DataPanel(logDataHolder.getProject(), false);
89 final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane();
90 myMessageDataPanel = new DataPanel(logDataHolder.getProject(), true) {
92 public Dimension getPreferredSize() {
93 Dimension size = super.getPreferredSize();
94 size.width = scrollPane.getViewport().getWidth() - 5;
98 scrollPane.setViewportView(myMessageDataPanel);
99 scrollPane.setBorder(UIUtil.makeChameleonBorder(scrollPane.getBorder(), new Condition<JScrollBar>() {
101 public boolean value(JScrollBar bar) {
102 return bar != null && bar.isVisible();
104 }, scrollPane.getVerticalScrollBar()));
106 myContainingBranchesPanel = new ContainingBranchesPanel();
107 myMessagePanel = new MessagePanel();
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());
118 setLayout(new CardLayout());
119 add(myLoadingPanel, STANDARD_LAYER);
120 add(myMessagePanel, MESSAGE_LAYER);
122 setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
123 setBackground(UIUtil.getTableBackground());
124 showMessage("No commits selected");
127 void updateDataPack(@NotNull VisiblePack dataPack) {
128 myDataPack = dataPack;
132 public void valueChanged(@Nullable ListSelectionEvent notUsed) {
133 int[] rows = myGraphTable.getSelectedRows();
134 if (rows.length < 1) {
135 showMessage("No commits selected");
137 else if (rows.length > 1) {
138 showMessage("Several commits selected");
141 ((CardLayout)getLayout()).show(this, STANDARD_LAYER);
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");
150 if (commitData instanceof LoadingDetails) {
151 myLoadingPanel.startLoading();
152 myHashAuthorPanel.setData(null);
153 myMessageDataPanel.setData(null);
154 myRefsPanel.setRefs(Collections.<VcsRef>emptyList());
157 myLoadingPanel.stopLoading();
158 myHashAuthorPanel.setData(commitData);
159 myMessageDataPanel.setData(commitData);
160 myRefsPanel.setRefs(sortRefs(hash, commitData.getRoot()));
163 List<String> branches = null;
164 if (!(commitData instanceof LoadingDetails)) {
165 branches = myLogDataHolder.getContainingBranchesGetter().requestContainingBranches(commitData.getRoot(), hash);
167 myContainingBranchesPanel.setBranches(branches);
171 private void showMessage(String text) {
172 myLoadingPanel.stopLoading();
173 ((CardLayout)getLayout()).show(this, MESSAGE_LAYER);
174 myMessagePanel.setText(text);
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());
183 private static class DataPanel extends JEditorPane {
185 @NotNull private final Project myProject;
186 private final boolean myMessageMode;
188 DataPanel(@NotNull Project project, boolean messageMode) {
189 super(UIUtil.HTML_MIME, "");
190 myMessageMode = messageMode;
193 addHyperlinkListener(BrowserHyperlinkListener.INSTANCE);
197 void setData(@Nullable VcsFullCommitDetails commit) {
198 if (commit == null) {
204 body = getMessageText(commit);
206 body = commit.getId().asString() + "<br/>" + getAuthorText(commit);
208 setText("<html><head>" + UIUtil.getCssFontDeclaration(UIUtil.getLabelFont()) + "</head><body>" + body + "</body></html>");
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);
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);
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())) {
236 if (commit.getAuthorTime() != commit.getTimestamp()) {
237 commitTime = " at " + DateFormatUtil.formatDateTime(commit.getTimestamp());
242 authorText += " (committed by " + commit.getCommitter().getName() + commitTime + ")";
244 else if (commit.getAuthorTime() != commit.getTimestamp()) {
245 authorText += " (committed at " + DateFormatUtil.formatDateTime(commit.getTimestamp()) + ")";
251 private static class ContainingBranchesPanel extends JPanel {
253 private final JComponent myLoadingComponent;
254 private final JTextField myBranchesList;
256 ContainingBranchesPanel() {
257 JLabel label = new JBLabel("Contained in branches: ") {
259 public Font getFont() {
260 return UIUtil.getLabelFont().deriveFont(Font.ITALIC);
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));
278 setLayout(new BorderLayout());
279 add(label, BorderLayout.WEST);
280 add(Box.createHorizontalGlue(), BorderLayout.EAST);
283 void setBranches(@Nullable List<String> branches) {
284 if (branches == null) {
285 remove(myBranchesList);
286 add(myLoadingComponent, BorderLayout.CENTER);
289 remove(myLoadingComponent);
290 myBranchesList.setText(StringUtil.join(branches, ", "));
291 add(myBranchesList, BorderLayout.CENTER);
298 private static class RefsPanel extends JPanel {
300 @NotNull private final RefPainter myRefPainter;
301 @NotNull private List<VcsRef> myRefs;
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));
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());
316 void setRefs(@NotNull List<VcsRef> refs) {
318 setVisible(!myRefs.isEmpty());
323 private static class MessagePanel extends NonOpaquePanel {
325 private final JLabel myLabel;
328 super(new BorderLayout());
329 myLabel = new JLabel();
330 myLabel.setForeground(UIUtil.getInactiveTextColor());
331 myLabel.setHorizontalAlignment(SwingConstants.CENTER);
332 myLabel.setVerticalAlignment(SwingConstants.CENTER);
336 void setText(String text) {
337 myLabel.setText(text);