A correct name for settings text padding. Allow to set alignment of a text fragment.
[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     if (partial) {
119       final String partialMarker = VcsBundle.message("committed.changes.partial.list") + " ";
120       append(partialMarker, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
121       descWidth += boldMetrics.stringWidth(partialMarker);
122     }
123
124     descWidth += fontMetrics.stringWidth(description);
125
126     int numberWidth = 0;
127     final AbstractVcs vcs = changeList.getVcs();
128     if (vcs != null) {
129       final CachingCommittedChangesProvider provider = vcs.getCachingCommittedChangesProvider();
130       if (provider != null && provider.getChangelistTitle() != null) {
131         String number = "#" + changeList.getNumber() + "  ";
132         numberWidth = fontMetrics.stringWidth(number);
133         descWidth += numberWidth;
134         append(number, SimpleTextAttributes.GRAY_ATTRIBUTES);
135       }
136     }
137
138     int branchWidth = 0;
139     String branch = changeList.getBranch();
140     if (branch != null) {
141       branch += " ";
142       branchWidth = italicMetrics.stringWidth(branch);
143       descWidth += branchWidth;
144       append(branch, SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
145     }
146
147     if (description.isEmpty() && !truncated) {
148       append(VcsBundle.message("committed.changes.empty.comment"), SimpleTextAttributes.GRAYED_ATTRIBUTES);
149       appendTextPadding(descMaxWidth);
150     }
151     else if (descMaxWidth < 0) {
152       myRenderer.appendTextWithLinks(description);
153     }
154     else if (descWidth < descMaxWidth && !truncated) {
155       myRenderer.appendTextWithLinks(description);
156       appendTextPadding(descMaxWidth);
157     }
158     else {
159       final String moreMarker = VcsBundle.message("changes.browser.details.marker");
160       int moreWidth = fontMetrics.stringWidth(moreMarker);
161       int remainingWidth = descMaxWidth - moreWidth - numberWidth - branchWidth;
162       description = truncateDescription(description, fontMetrics, remainingWidth);
163       myRenderer.appendTextWithLinks(description);
164       if (!StringUtil.isEmpty(description)) {
165         append(" ", SimpleTextAttributes.REGULAR_ATTRIBUTES);
166         append(moreMarker, SimpleTextAttributes.LINK_ATTRIBUTES, new CommittedChangesTreeBrowser.MoreLauncher(myProject, changeList));
167       } else if (remainingWidth > 0) {
168         append(moreMarker, SimpleTextAttributes.LINK_ATTRIBUTES, new CommittedChangesTreeBrowser.MoreLauncher(myProject, changeList));
169       }
170       // align value is for the latest added piece
171       appendTextPadding(descMaxWidth);
172     }
173
174     append(changeList.getCommitterName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
175     append(date, SimpleTextAttributes.REGULAR_ATTRIBUTES);
176   }
177
178   private static String trimLastWord(final String description) {
179     int pos = description.trim().lastIndexOf(' ');
180     if (pos >= 0) {
181       return description.substring(0, pos).trim();
182     }
183     return description.substring(0, description.length()-1);
184   }
185
186   @NotNull
187   public Dimension getPreferredSize() {
188     return new Dimension(2000, super.getPreferredSize().height);
189   }
190
191   public static int getRowX(JTree tree, int depth) {
192     if (tree == null) return 0;
193     final TreeUI ui = tree.getUI();
194     if (ui instanceof BasicTreeUI) {
195       final BasicTreeUI treeUI = ((BasicTreeUI)ui);
196       return (treeUI.getLeftChildIndent() + treeUI.getRightChildIndent()) * depth;
197     }
198
199     final int leftIndent = UIUtil.getTreeLeftChildIndent();
200     final int rightIndent = UIUtil.getTreeRightChildIndent();
201
202     return (leftIndent + rightIndent) * depth;
203   }
204 }