1 package com.atlassian.theplugin.idea.crucible.tree;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import com.atlassian.theplugin.commons.crucible.CrucibleServerFacadeImpl;
19 import com.atlassian.theplugin.commons.crucible.ValueNotYetInitialized;
20 import com.atlassian.theplugin.commons.crucible.api.model.CrucibleFileInfo;
21 import com.atlassian.theplugin.commons.crucible.api.model.Reviewer;
22 import com.atlassian.theplugin.commons.crucible.api.model.VersionedComment;
23 import com.atlassian.theplugin.commons.exception.ServerPasswordNotProvidedException;
24 import com.atlassian.theplugin.commons.remoteapi.RemoteApiException;
25 import com.atlassian.theplugin.commons.util.Logger;
26 import com.atlassian.theplugin.idea.IdeaHelper;
27 import com.atlassian.theplugin.idea.ProgressAnimationProvider;
28 import com.atlassian.theplugin.idea.crucible.CrucibleConstants;
29 import com.atlassian.theplugin.idea.crucible.CrucibleFilteredModelProvider;
30 import com.atlassian.theplugin.idea.crucible.ReviewData;
31 import com.atlassian.theplugin.idea.crucible.comments.CrucibleReviewActionListener;
32 import com.atlassian.theplugin.idea.ui.tree.AtlassianTreeModel;
33 import com.atlassian.theplugin.idea.ui.tree.AtlassianTreeNode;
34 import com.atlassian.theplugin.idea.ui.tree.Filter;
35 import com.atlassian.theplugin.idea.ui.tree.NodeSearchAlgorithm;
36 import com.atlassian.theplugin.idea.ui.tree.file.CrucibleChangeSetTitleNode;
37 import com.atlassian.theplugin.idea.ui.tree.file.CrucibleFileNode;
38 import com.atlassian.theplugin.idea.ui.tree.file.FileTreeModelBuilder;
39 import com.atlassian.theplugin.util.PluginUtil;
40 import com.intellij.openapi.application.ApplicationManager;
41 import com.intellij.openapi.project.Project;
42 import com.intellij.util.ui.UIUtil;
43
44 import javax.swing.*;
45 import java.awt.*;
46 import java.util.List;
47
48 public final class ReviewItemTreePanel extends JPanel {
49
50
51 private AtlassianTreeWithToolbar reviewFilesTree = null;
52 private static final int WIDTH = 150;
53 private static final int HEIGHT = 250;
54
55 public static final Logger LOGGER = PluginUtil.getLogger();
56
57 private ProgressAnimationProvider progressAnimation = new ProgressAnimationProvider();
58 private JLabel statusLabel;
59 private CrucibleFilteredModelProvider.FILTER filter;
60
61 public ReviewItemTreePanel(final Project project, final CrucibleFilteredModelProvider.FILTER filter) {
62 initLayout();
63 final CrucibleReviewActionListener listener = new MyReviewActionListener(project);
64 IdeaHelper.getReviewActionEventBroker(project).registerListener(listener);
65 this.filter = filter;
66 }
67
68 private void initLayout() {
69 setLayout(new BorderLayout());
70 setMinimumSize(new Dimension(WIDTH, HEIGHT));
71 add(getReviewItemTree(), BorderLayout.CENTER);
72 statusLabel = new JLabel();
73 statusLabel.setBackground(UIUtil.getTreeTextBackground());
74 add(statusLabel, BorderLayout.SOUTH);
75 }
76
77 public JPanel getReviewItemTree() {
78 if (reviewFilesTree == null) {
79 reviewFilesTree = new AtlassianTreeWithToolbar("ThePlugin.Crucible.ReviewFileListToolBar");
80 reviewFilesTree.setRootVisible(false);
81 }
82 return reviewFilesTree;
83 }
84
85 @Override
86 public void setEnabled(boolean b) {
87 super.setEnabled(b);
88 getReviewItemTree().setEnabled(b);
89 }
90
91
92 public ProgressAnimationProvider getProgressAnimation() {
93 return progressAnimation;
94 }
95
96 public void filterTreeNodes(CrucibleFilteredModelProvider.FILTER aFilter) {
97 this.filter = aFilter;
98 ((CrucibleFilteredModelProvider) reviewFilesTree.getModelProvider()).setType(aFilter);
99 reviewFilesTree.triggerModelUpdated();
100 reviewFilesTree.revalidate();
101 reviewFilesTree.repaint();
102 }
103
104 private final class MyReviewActionListener extends CrucibleReviewActionListener {
105 private Project project;
106
107 private MyReviewActionListener(Project project) {
108 this.project = project;
109 }
110
111 @Override
112 public void focusOnFile(final ReviewData review, final CrucibleFileInfo file) {
113 ApplicationManager.getApplication().invokeLater(new Runnable() {
114 public void run() {
115 AtlassianTreeNode node = reviewFilesTree.getModel().locateNode(new NodeSearchAlgorithm() {
116 @Override
117 public boolean check(final AtlassianTreeNode node) {
118 if (node instanceof CrucibleFileNode) {
119 CrucibleFileNode anode = (CrucibleFileNode) node;
120 if (anode.getReview().equals(review) && anode.getFile().equals(file)) {
121 return true;
122 }
123 }
124 return false;
125 }
126 });
127 reviewFilesTree.focusOnNode(node);
128 }
129 });
130
131 }
132
133 @Override
134 public void focusOnVersionedCommentEvent(final ReviewData review, final CrucibleFileInfo file,
135 final VersionedComment comment) {
136 focusOnFile(review, file);
137 }
138
139 @Override
140 public void focusOnLineCommentEvent(final ReviewData review, final CrucibleFileInfo file,
141 final VersionedComment comment, final boolean openIfClosed) {
142 focusOnFile(review, file);
143 }
144
145 @Override
146 public void focusOnReview(final ReviewData review) {
147 ApplicationManager.getApplication().invokeLater(new Runnable() {
148 public void run() {
149 AtlassianTreeNode node = reviewFilesTree.getModel().locateNode(new NodeSearchAlgorithm() {
150 @Override
151 public boolean check(final AtlassianTreeNode node) {
152 if (node instanceof CrucibleChangeSetTitleNode) {
153 CrucibleChangeSetTitleNode anode = (CrucibleChangeSetTitleNode) node;
154 if (anode.getReview().equals(review)) {
155 return true;
156 }
157 }
158 return false;
159 }
160 });
161 reviewFilesTree.focusOnNode(node);
162 }
163 });
164 }
165
166 @Override
167 public void showReview(final ReviewData reviewItem) {
168 List<CrucibleFileInfo> files;
169 try {
170 files = reviewItem.getFiles();
171 } catch (ValueNotYetInitialized valueNotYetInitialized) {
172 try {
173 files = CrucibleServerFacadeImpl.getInstance().getFiles(reviewItem.getServer(), reviewItem.getPermId());
174 } catch (RemoteApiException e) {
175 IdeaHelper.handleRemoteApiException(project, e);
176 return;
177 } catch (ServerPasswordNotProvidedException e) {
178 IdeaHelper.handleMissingPassword(e);
179 return;
180 }
181 }
182 final List<CrucibleFileInfo> files1 = files;
183 EventQueue.invokeLater(new MyRunnable(reviewItem, files1));
184 }
185
186
187 private String createGeneralInfoText(final ReviewData reviewItem) {
188 final StringBuilder buffer = new StringBuilder();
189 buffer.append("<html>");
190 buffer.append("<body>");
191 buffer.append(reviewItem.getAuthor().getDisplayName());
192 buffer.append(" ");
193 buffer.append("<font size=-1 color=");
194 buffer.append(CrucibleConstants.CRUCIBLE_AUTH_COLOR);
195 buffer.append(">AUTH</font>");
196 buffer.append(" ");
197 if (!reviewItem.getCreator().equals(reviewItem.getModerator())) {
198 buffer.append(reviewItem.getModerator().getDisplayName());
199 }
200 buffer.append(" ");
201 buffer.append("<font size=-1 color=");
202 buffer.append(CrucibleConstants.CRUCIBLE_MOD_COLOR);
203 buffer.append(">MOD</font>");
204 int i = 0;
205 List<Reviewer> reviewers;
206 try {
207 reviewers = reviewItem.getReviewers();
208 if (reviewers != null) {
209 buffer.append("<br>");
210 for (Reviewer reviewer : reviewers) {
211 if (i > 0) {
212 buffer.append(", ");
213 }
214 buffer.append(reviewer.getDisplayName());
215 i++;
216 }
217 }
218 } catch (ValueNotYetInitialized valueNotYetInitialized) {
219
220 }
221 buffer.append("</body>");
222 buffer.append("</html>");
223
224 return buffer.toString();
225 }
226
227 private class MyRunnable implements Runnable {
228 private final ReviewData reviewItem;
229 private final List<CrucibleFileInfo> files1;
230
231 public MyRunnable(final ReviewData reviewItem, final List<CrucibleFileInfo> files1) {
232 this.reviewItem = reviewItem;
233 this.files1 = files1;
234 }
235
236 public void run() {
237
238 statusLabel.setText(createGeneralInfoText(reviewItem));
239 ModelProvider modelProvider = new ModelProvider() {
240
241 @Override
242 public AtlassianTreeModel getModel(final AtlassianTreeWithToolbar.STATE state) {
243 switch (state) {
244 case DIRED:
245 return FileTreeModelBuilder.buildTreeModelFromCrucibleChangeSet(project, reviewItem, files1);
246 case FLAT:
247 return FileTreeModelBuilder.buildFlatModelFromCrucibleChangeSet(project, reviewItem, files1);
248 default:
249 throw new IllegalStateException("Unknown model requested");
250 }
251 }
252 };
253 CrucibleFilteredModelProvider provider = new MyCrucibleFilteredModelProvider(modelProvider, filter);
254 reviewFilesTree.setModelProvider(provider);
255 reviewFilesTree.setRootVisible(true);
256 }
257 }
258 }
259
260 private static class MyCrucibleFilteredModelProvider extends CrucibleFilteredModelProvider {
261 private static final Filter COMMENT_FILTER = new Filter() {
262 @Override
263 public boolean isValid(final AtlassianTreeNode node) {
264 if (node instanceof CrucibleFileNode) {
265 CrucibleFileNode anode = (CrucibleFileNode) node;
266 try {
267 return anode.getFile().getNumberOfComments() > 0;
268 } catch (ValueNotYetInitialized valueNotYetInitialized) {
269 return false;
270 }
271 }
272 return true;
273 }
274 };
275
276 public MyCrucibleFilteredModelProvider(final ModelProvider modelProvider, final FILTER filter) {
277 super(modelProvider, filter);
278 }
279
280 @Override
281 public Filter getFilter(final FILTER type) {
282 switch (type) {
283 case FILES_ALL:
284 return Filter.ALL;
285 case FILES_WITH_COMMENTS_ONLY:
286 return COMMENT_FILTER;
287 default:
288 throw new IllegalStateException("Unknows filtering requested: " + type.toString());
289 }
290 }
291 }
292
293 }