Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / committed / CommittedChangeListRenderer.java
1 /*
2  * Copyright 2000-2013 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.openapi.vcs.changes.committed;
17
18 import com.intellij.openapi.project.Project;
19 import com.intellij.openapi.util.Pair;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.openapi.vcs.AbstractVcs;
22 import com.intellij.openapi.vcs.CachingCommittedChangesProvider;
23 import com.intellij.openapi.vcs.VcsBundle;
24 import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkRenderer;
25 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
26 import com.intellij.ui.ColoredTreeCellRenderer;
27 import com.intellij.ui.SimpleTextAttributes;
28 import com.intellij.util.text.DateFormatUtil;
29 import com.intellij.util.ui.UIUtil;
30 import org.jetbrains.annotations.NotNull;
31
32 import javax.swing.*;
33 import javax.swing.plaf.TreeUI;
34 import javax.swing.plaf.basic.BasicTreeUI;
35 import javax.swing.tree.DefaultMutableTreeNode;
36 import java.awt.*;
37 import java.util.Date;
38 import java.util.List;
39
40 public class CommittedChangeListRenderer extends ColoredTreeCellRenderer {
41   private final IssueLinkRenderer myRenderer;
42   private final List<CommittedChangeListDecorator> myDecorators;
43   private final Project myProject;
44   private int myDateWidth;
45   private int myFontSize;
46
47   public CommittedChangeListRenderer(final Project project, final List<CommittedChangeListDecorator> decorators) {
48     myProject = project;
49     myRenderer = new IssueLinkRenderer(project, this);
50     myDecorators = decorators;
51     myDateWidth = 0;
52     myFontSize = -1;
53   }
54
55   public static String getDateOfChangeList(final Date date) {
56     return DateFormatUtil.formatPrettyDateTime(date);
57   }
58
59   public static Pair<String, Boolean> getDescriptionOfChangeList(final String text) {
60     return new Pair<String, Boolean>(text.replaceAll("\n", " // "), text.contains("\n"));
61   }
62
63   public static String truncateDescription(final String initDescription, final FontMetrics fontMetrics, int maxWidth) {
64     String description = initDescription;
65     int descWidth = fontMetrics.stringWidth(description);
66     while(description.length() > 0 && (descWidth > maxWidth)) {
67       description = trimLastWord(description);
68       descWidth = fontMetrics.stringWidth(description + " ");
69     }
70     return description;
71   }
72
73   public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
74     customize(tree, value, selected, expanded, leaf, row, hasFocus);
75   }
76
77   public void customize(JComponent tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
78     DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
79     if (node.getUserObject() instanceof CommittedChangeList) {
80       CommittedChangeList changeList = (CommittedChangeList) node.getUserObject();
81
82       renderChangeList(tree, changeList);
83     }
84     else if (node.getUserObject() != null) {
85       append(node.getUserObject().toString(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
86     }
87   }
88
89   public void renderChangeList(JComponent tree, CommittedChangeList changeList) {
90     final Container parent = tree.getParent();
91     final int rowX = getRowX(myTree, 2);
92     int availableWidth = parent == null ? 100 : parent.getWidth() - rowX;
93     String date = ", " + getDateOfChangeList(changeList.getCommitDate());
94     final FontMetrics fontMetrics = tree.getFontMetrics(tree.getFont());
95     final FontMetrics boldMetrics = tree.getFontMetrics(tree.getFont().deriveFont(Font.BOLD));
96     final FontMetrics italicMetrics = tree.getFontMetrics(tree.getFont().deriveFont(Font.ITALIC));
97     if (myDateWidth <= 0 || (fontMetrics.getFont().getSize() != myFontSize)) {
98       myDateWidth = Math.max(fontMetrics.stringWidth(", Yesterday 00:00 PM "), fontMetrics.stringWidth(", 00/00/00 00:00 PM "));
99       myDateWidth = Math.max(myDateWidth, fontMetrics.stringWidth(getDateOfChangeList(new Date(2000, 11, 31))));
100       myFontSize = fontMetrics.getFont().getSize();
101     }
102     int dateCommitterSize = myDateWidth + boldMetrics.stringWidth(changeList.getCommitterName());
103
104     final Pair<String, Boolean> descriptionInfo = getDescriptionOfChangeList(changeList.getName().trim());
105     boolean truncated = descriptionInfo.getSecond().booleanValue();
106     String description = descriptionInfo.getFirst();
107
108     for (CommittedChangeListDecorator decorator : myDecorators) {
109       final Icon icon = decorator.decorate(changeList);
110       if (icon != null) {
111         setIcon(icon);
112       }
113     }
114
115     int descMaxWidth = availableWidth - dateCommitterSize - 8;
116     boolean partial = (changeList instanceof ReceivedChangeList) && ((ReceivedChangeList)changeList).isPartial();
117     int descWidth = 0;
118     int partialMarkerWidth = 0;
119     if (partial) {
120       final String partialMarker = VcsBundle.message("committed.changes.partial.list") + " ";
121       append(partialMarker, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
122       partialMarkerWidth = boldMetrics.stringWidth(partialMarker);
123       descWidth += partialMarkerWidth;
124     }
125
126     descWidth += fontMetrics.stringWidth(description);
127
128     int numberWidth = 0;
129     final AbstractVcs vcs = changeList.getVcs();
130     if (vcs != null) {
131       final CachingCommittedChangesProvider provider = vcs.getCachingCommittedChangesProvider();
132       if (provider != null && provider.getChangelistTitle() != null) {
133         String number = "#" + changeList.getNumber() + "  ";
134         numberWidth = fontMetrics.stringWidth(number);
135         descWidth += numberWidth;
136         append(number, SimpleTextAttributes.GRAY_ATTRIBUTES);
137       }
138     }
139
140     int branchWidth = 0;
141     String branch = changeList.getBranch();
142     if (branch != null) {
143       branch += " ";
144       branchWidth = italicMetrics.stringWidth(branch);
145       descWidth += branchWidth;
146       append(branch, SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
147     }
148
149     if (description.isEmpty() && !truncated) {
150       append(VcsBundle.message("committed.changes.empty.comment"), SimpleTextAttributes.GRAYED_ATTRIBUTES);
151       appendTextPadding(descMaxWidth);
152     }
153     else if (descMaxWidth < 0) {
154       myRenderer.appendTextWithLinks(description);
155     }
156     else if (descWidth < descMaxWidth && !truncated) {
157       myRenderer.appendTextWithLinks(description);
158       appendTextPadding(descMaxWidth);
159     }
160     else {
161       final String moreMarker = VcsBundle.message("changes.browser.details.marker");
162       int moreWidth = fontMetrics.stringWidth(moreMarker);
163       int remainingWidth = descMaxWidth - moreWidth - numberWidth - branchWidth - partialMarkerWidth;
164       description = truncateDescription(description, fontMetrics, remainingWidth);
165       myRenderer.appendTextWithLinks(description);
166       if (!StringUtil.isEmpty(description)) {
167         append(" ", SimpleTextAttributes.REGULAR_ATTRIBUTES);
168         append(moreMarker, SimpleTextAttributes.LINK_ATTRIBUTES, new CommittedChangesTreeBrowser.MoreLauncher(myProject, changeList));
169       } else if (remainingWidth > 0) {
170         append(moreMarker, SimpleTextAttributes.LINK_ATTRIBUTES, new CommittedChangesTreeBrowser.MoreLauncher(myProject, changeList));
171       }
172       // align value is for the latest added piece
173       appendTextPadding(descMaxWidth);
174     }
175
176     append(changeList.getCommitterName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
177     append(date, SimpleTextAttributes.REGULAR_ATTRIBUTES);
178   }
179
180   private static String trimLastWord(final String description) {
181     int pos = description.trim().lastIndexOf(' ');
182     if (pos >= 0) {
183       return description.substring(0, pos).trim();
184     }
185     return description.substring(0, description.length()-1);
186   }
187
188   @NotNull
189   public Dimension getPreferredSize() {
190     return new Dimension(2000, super.getPreferredSize().height);
191   }
192
193   public static int getRowX(JTree tree, int depth) {
194     if (tree == null) return 0;
195     final TreeUI ui = tree.getUI();
196     if (ui instanceof BasicTreeUI) {
197       final BasicTreeUI treeUI = ((BasicTreeUI)ui);
198       return (treeUI.getLeftChildIndent() + treeUI.getRightChildIndent()) * depth;
199     }
200
201     final int leftIndent = UIUtil.getTreeLeftChildIndent();
202     final int rightIndent = UIUtil.getTreeRightChildIndent();
203
204     return (leftIndent + rightIndent) * depth;
205   }
206 }