2 * Copyright 2000-2013 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.openapi.vcs.changes.committed;
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;
33 import javax.swing.plaf.TreeUI;
34 import javax.swing.plaf.basic.BasicTreeUI;
35 import javax.swing.tree.DefaultMutableTreeNode;
37 import java.util.Date;
38 import java.util.List;
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;
47 public CommittedChangeListRenderer(final Project project, final List<CommittedChangeListDecorator> decorators) {
49 myRenderer = new IssueLinkRenderer(project, this);
50 myDecorators = decorators;
55 public static String getDateOfChangeList(final Date date) {
56 return DateFormatUtil.formatPrettyDateTime(date);
59 public static Pair<String, Boolean> getDescriptionOfChangeList(final String text) {
60 return new Pair<String, Boolean>(text.replaceAll("\n", " // "), text.contains("\n"));
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 + " ");
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);
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();
82 renderChangeList(tree, changeList);
84 else if (node.getUserObject() != null) {
85 append(node.getUserObject().toString(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
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();
102 int dateCommitterSize = myDateWidth + boldMetrics.stringWidth(changeList.getCommitterName());
104 final Pair<String, Boolean> descriptionInfo = getDescriptionOfChangeList(changeList.getName().trim());
105 boolean truncated = descriptionInfo.getSecond().booleanValue();
106 String description = descriptionInfo.getFirst();
108 for (CommittedChangeListDecorator decorator : myDecorators) {
109 final Icon icon = decorator.decorate(changeList);
115 int descMaxWidth = availableWidth - dateCommitterSize - 8;
116 boolean partial = (changeList instanceof ReceivedChangeList) && ((ReceivedChangeList)changeList).isPartial();
118 int partialMarkerWidth = 0;
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;
126 descWidth += fontMetrics.stringWidth(description);
129 final AbstractVcs vcs = changeList.getVcs();
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);
141 String branch = changeList.getBranch();
142 if (branch != null) {
144 branchWidth = italicMetrics.stringWidth(branch);
145 descWidth += branchWidth;
146 append(branch, SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
149 if (description.isEmpty() && !truncated) {
150 append(VcsBundle.message("committed.changes.empty.comment"), SimpleTextAttributes.GRAYED_ATTRIBUTES);
151 appendTextPadding(descMaxWidth);
153 else if (descMaxWidth < 0) {
154 myRenderer.appendTextWithLinks(description);
156 else if (descWidth < descMaxWidth && !truncated) {
157 myRenderer.appendTextWithLinks(description);
158 appendTextPadding(descMaxWidth);
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));
172 // align value is for the latest added piece
173 appendTextPadding(descMaxWidth);
176 append(changeList.getCommitterName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
177 append(date, SimpleTextAttributes.REGULAR_ATTRIBUTES);
180 private static String trimLastWord(final String description) {
181 int pos = description.trim().lastIndexOf(' ');
183 return description.substring(0, pos).trim();
185 return description.substring(0, description.length()-1);
189 public Dimension getPreferredSize() {
190 return new Dimension(2000, super.getPreferredSize().height);
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;
201 final int leftIndent = UIUtil.getTreeLeftChildIndent();
202 final int rightIndent = UIUtil.getTreeRightChildIndent();
204 return (leftIndent + rightIndent) * depth;