1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.atlassian.theplugin.idea;
18
19
20 import com.atlassian.theplugin.commons.crucible.CrucibleServerFacade;
21 import com.atlassian.theplugin.commons.crucible.CrucibleServerFacadeImpl;
22 import com.atlassian.theplugin.commons.crucible.CrucibleVersion;
23 import com.atlassian.theplugin.commons.crucible.ValueNotYetInitialized;
24 import com.atlassian.theplugin.commons.crucible.api.model.*;
25 import com.atlassian.theplugin.commons.exception.ServerPasswordNotProvidedException;
26 import com.atlassian.theplugin.commons.remoteapi.RemoteApiException;
27 import com.atlassian.theplugin.idea.crucible.CommentEditForm;
28 import com.atlassian.theplugin.idea.crucible.CrucibleFilteredModelProvider;
29 import com.atlassian.theplugin.idea.crucible.CrucibleHelper;
30 import com.atlassian.theplugin.idea.crucible.ReviewData;
31 import com.atlassian.theplugin.idea.crucible.comments.CrucibleReviewActionListener;
32 import com.atlassian.theplugin.idea.crucible.comments.ReviewActionEventBroker;
33 import com.atlassian.theplugin.idea.crucible.events.*;
34 import com.atlassian.theplugin.idea.crucible.tree.ReviewItemTreePanel;
35 import com.intellij.openapi.actionSystem.DataProvider;
36 import com.intellij.openapi.application.ApplicationManager;
37 import com.intellij.openapi.editor.Editor;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.ui.DialogWrapper;
40 import com.intellij.openapi.ui.Splitter;
41 import com.intellij.openapi.util.Key;
42 import com.intellij.openapi.wm.ToolWindow;
43 import com.intellij.openapi.wm.ToolWindowAnchor;
44 import com.intellij.openapi.wm.ToolWindowManager;
45 import com.intellij.peer.PeerFactory;
46 import com.intellij.ui.content.Content;
47 import com.intellij.util.ui.UIUtil;
48 import org.jetbrains.annotations.NonNls;
49 import org.jetbrains.annotations.Nullable;
50
51 import javax.swing.*;
52 import java.awt.*;
53 import java.util.Date;
54 import java.util.List;
55
56 public final class CrucibleReviewWindow extends JPanel implements DataProvider {
57 public static final String TOOL_WINDOW_TITLE = "Crucible Review";
58 private static final Key<CrucibleReviewWindow> WINDOW_PROJECT_KEY
59 = Key.create(CrucibleReviewWindow.class.getName());
60 private Project project;
61 private static final float SPLIT_RATIO = 0.3f;
62 protected static final Dimension ED_PANE_MINE_SIZE = new Dimension(200, 200);
63 protected ProgressAnimationProvider progressAReviewActionEventBrokernimation = new ProgressAnimationProvider();
64 private CrucibleVersion crucibleVersion = CrucibleVersion.UNKNOWN;
65 private ReviewItemTreePanel reviewItemTreePanel;
66 private CommentTreePanel reviewComentsPanel;
67 private ReviewActionEventBroker eventBroker;
68 private static final int LEFT_WIDTH = 150;
69 private static final int LEFT_HEIGHT = 250;
70 private ProgressAnimationProvider progressAnimation = new ProgressAnimationProvider();
71 private CrucibleFilteredModelProvider.FILTER filter = CrucibleFilteredModelProvider.FILTER.FILES_ALL;
72
73
74 protected String getInitialMessage() {
75
76 return "Waiting for Crucible review info.";
77 }
78
79 public static CrucibleReviewWindow getInstance(Project project) {
80
81 CrucibleReviewWindow window = project.getUserData(WINDOW_PROJECT_KEY);
82
83 if (window == null) {
84 window = new CrucibleReviewWindow(project);
85 project.putUserData(WINDOW_PROJECT_KEY, window);
86 }
87 return window;
88 }
89
90 public ReviewItemTreePanel getReviewItemTreePanel() {
91 return reviewItemTreePanel;
92 }
93
94 public CommentTreePanel getReviewComentsPanel() {
95 return reviewComentsPanel;
96 }
97
98 public void showCrucibleReviewWindow() {
99
100 ToolWindowManager twm = ToolWindowManager.getInstance(this.project);
101 ToolWindow toolWindow = twm.getToolWindow(TOOL_WINDOW_TITLE);
102 if (toolWindow == null) {
103 toolWindow = twm.registerToolWindow(TOOL_WINDOW_TITLE, true, ToolWindowAnchor.BOTTOM);
104 toolWindow.setIcon(PluginToolWindow.ICON_CRUCIBLE);
105 }
106
107 Content content = toolWindow.getContentManager().findContent(WINDOW_PROJECT_KEY.toString());
108
109 if (content == null) {
110
111 PeerFactory peerFactory = PeerFactory.getInstance();
112 content = peerFactory.getContentFactory().createContent(this, WINDOW_PROJECT_KEY.toString(), false);
113 content.setIcon(PluginToolWindow.ICON_CRUCIBLE);
114 content.putUserData(com.intellij.openapi.wm.ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE);
115 toolWindow.getContentManager().addContent(content);
116 }
117
118 toolWindow.getContentManager().setSelectedContent(content);
119 toolWindow.show(null);
120 }
121
122 private CrucibleReviewWindow(Project project) {
123 super(new BorderLayout());
124
125 this.project = project;
126 setBackground(UIUtil.getTreeTextBackground());
127 reviewItemTreePanel = new ReviewItemTreePanel(project, filter);
128 Splitter splitter = new Splitter(false, SPLIT_RATIO);
129 splitter.setShowDividerControls(true);
130 reviewItemTreePanel.getProgressAnimation().configure(reviewItemTreePanel, reviewItemTreePanel, BorderLayout.CENTER);
131 splitter.setFirstComponent(reviewItemTreePanel);
132 splitter.setHonorComponentsMinimumSize(true);
133 reviewComentsPanel = new CommentTreePanel(project, filter);
134 splitter.setSecondComponent(reviewComentsPanel);
135 add(splitter, BorderLayout.CENTER);
136
137
138 eventBroker = IdeaHelper.getReviewActionEventBroker(project);
139 eventBroker.registerListener(new MyAgent(project));
140
141
142 progressAnimation.configure(this, reviewItemTreePanel, BorderLayout.CENTER);
143 }
144
145
146 protected JScrollPane setupPane(JEditorPane pane, String initialText) {
147 pane.setText(initialText);
148 JScrollPane scrollPane = new JScrollPane(pane,
149 JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
150 scrollPane.setWheelScrollingEnabled(true);
151 return scrollPane;
152 }
153
154 public ProgressAnimationProvider getProgressAnimation() {
155 return progressAnimation;
156 }
157
158 public CrucibleVersion getCrucibleVersion() {
159 return crucibleVersion;
160 }
161
162
163 @Nullable
164 public Object getData(@NonNls final String dataId) {
165 if (dataId.equals(Constants.FILE_TREE)) {
166 return reviewItemTreePanel.getReviewItemTree();
167 } else if (dataId.equals(Constants.CRUCIBLE_BOTTOM_WINDOW)) {
168 return this;
169 } else if (dataId.equals(Constants.CRUCIBLE_COMMENT_TREE)) {
170 return reviewComentsPanel.getCommentTree();
171 }
172 return null;
173 }
174
175 public void switchFilter() {
176 filter = filter.getNextState();
177 getReviewComentsPanel().filterTreeNodes(filter);
178 getReviewItemTreePanel().filterTreeNodes(filter);
179 }
180
181 private final class MyAgent extends CrucibleReviewActionListener {
182 private final CrucibleServerFacade facade = CrucibleServerFacadeImpl.getInstance();
183 private final ReviewActionEventBroker eventBroker;
184 private Project project;
185
186 public MyAgent(final Project project) {
187 super();
188 this.project = project;
189 eventBroker = IdeaHelper.getReviewActionEventBroker(this.project);
190 }
191
192 @Override
193 public void showReview(final ReviewData reviewData) {
194 EventQueue.invokeLater(new Runnable() {
195 public void run() {
196 showCrucibleReviewWindow();
197 }
198 });
199 }
200
201 @Override
202 public void focusOnLineCommentEvent(final ReviewData review, final CrucibleFileInfo file,
203 final VersionedComment comment, final boolean openIfClosed) {
204 EventQueue.invokeLater(new Runnable() {
205 public void run() {
206 Editor editor = CrucibleHelper.getEditorForCrucibleFile(review, file);
207 if (editor != null) {
208 CrucibleHelper.openFileOnComment(project, review, file, comment);
209 return;
210 }
211 if (openIfClosed) {
212 CrucibleHelper.openFileOnComment(project, review, file, comment);
213 }
214 }
215 });
216 }
217
218 @Override
219 public void showDiff(final CrucibleFileInfo file) {
220 CrucibleHelper.showRevisionDiff(project, file);
221 }
222
223 @Override
224 public void showFile(final ReviewData review, final CrucibleFileInfo file) {
225 CrucibleHelper.showVirtualFileWithComments(project, review, file);
226 }
227
228 @Override
229 public void aboutToAddLineComment(final ReviewData review, final CrucibleFileInfo file, final Editor editor,
230 final int start, final int end) {
231 ApplicationManager.getApplication().invokeLater(new Runnable() {
232 public void run() {
233 VersionedCommentBean newComment = new VersionedCommentBean();
234 CommentEditForm dialog = new CommentEditForm(project, review, newComment,
235 CrucibleHelper.getMetricsForReview(project, review));
236 dialog.pack();
237 dialog.setModal(true);
238 dialog.show();
239 if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
240 newComment.setCreateDate(new Date());
241 newComment.setAuthor(new UserBean(review.getServer().getUsername()));
242 newComment.setToStartLine(start);
243 newComment.setToEndLine(end);
244 eventBroker.trigger(new VersionedCommentAboutToAdd(CrucibleReviewActionListener.ANONYMOUS, review,
245 file, newComment));
246 }
247 }
248 });
249 }
250
251 @Override
252 public void aboutToPublishGeneralComment(final ReviewData review, final GeneralComment comment) {
253 try {
254 facade.publishComment(review.getServer(), review.getPermId(), comment.getPermId());
255
256 if (comment instanceof GeneralCommentBean) {
257 ((GeneralCommentBean) comment).setDraft(false);
258 }
259 eventBroker.trigger(new GeneralCommentPublished(this, review, comment));
260 } catch (RemoteApiException e) {
261 IdeaHelper.handleRemoteApiException(project, e);
262 } catch (ServerPasswordNotProvidedException e) {
263 IdeaHelper.handleMissingPassword(e);
264 }
265 }
266
267 @Override
268 public void aboutToPublishVersionedComment(final ReviewData review, final CrucibleFileInfo file,
269 final VersionedComment comment) {
270 try {
271 facade.publishComment(review.getServer(), review.getPermId(), comment.getPermId());
272
273 if (comment instanceof VersionedCommentBean) {
274 ((VersionedCommentBean) comment).setDraft(false);
275 }
276 eventBroker.trigger(new VersionedCommentPublished(this, review, file, comment));
277 } catch (RemoteApiException e) {
278 IdeaHelper.handleRemoteApiException(project, e);
279 } catch (ServerPasswordNotProvidedException e) {
280 IdeaHelper.handleMissingPassword(e);
281 }
282 }
283
284
285 @Override
286 public void aboutToAddGeneralComment(final ReviewData review, final GeneralComment newComment) {
287 try {
288 GeneralComment comment = facade.addGeneralComment(review.getServer(), review.getPermId(),
289 newComment);
290 eventBroker.trigger(new GeneralCommentAdded(this, review, comment));
291 } catch (RemoteApiException e) {
292 IdeaHelper.handleRemoteApiException(project, e);
293 } catch (ServerPasswordNotProvidedException e) {
294 IdeaHelper.handleMissingPassword(e);
295 }
296 }
297
298 @Override
299 public void aboutToAddGeneralCommentReply(ReviewData review, GeneralComment parentComment,
300 GeneralComment newComment) {
301 try {
302 GeneralComment comment = facade.addGeneralCommentReply(review.getServer(), review.getPermId(),
303 parentComment.getPermId(), newComment);
304 eventBroker.trigger(new GeneralCommentReplyAdded(this, review, parentComment, comment));
305 } catch (RemoteApiException e) {
306 IdeaHelper.handleRemoteApiException(project, e);
307 } catch (ServerPasswordNotProvidedException e) {
308 IdeaHelper.handleMissingPassword(e);
309 }
310 }
311
312 @Override
313 public void aboutToAddVersionedComment(ReviewData review, CrucibleFileInfo file,
314 VersionedComment comment) {
315 try {
316 VersionedComment newComment = facade.addVersionedComment(review.getServer(), review.getPermId(),
317 file.getPermId(), comment);
318 List<VersionedComment> comments;
319 try {
320 comments = file.getVersionedComments();
321 } catch (ValueNotYetInitialized valueNotYetInitialized) {
322 comments = facade.getVersionedComments(review.getServer(), review.getPermId(),
323 file.getPermId());
324 ((CrucibleFileInfoImpl) file).setVersionedComments(comments);
325 }
326 comments.add(newComment);
327 eventBroker.trigger(new VersionedCommentAdded(this, review, file, newComment));
328 } catch (RemoteApiException e) {
329 IdeaHelper.handleRemoteApiException(project, e);
330 } catch (ServerPasswordNotProvidedException e) {
331 IdeaHelper.handleMissingPassword(e);
332 }
333 }
334
335 @Override
336 public void aboutToAddVersionedCommentReply(ReviewData review, CrucibleFileInfo file,
337 VersionedComment parentComment, VersionedComment comment) {
338
339 try {
340 VersionedComment newComment = facade.addVersionedCommentReply(review.getServer(), review.getPermId(),
341 parentComment.getPermId(), comment);
342 eventBroker.trigger(new VersionedCommentReplyAdded(this, review, file, parentComment, newComment));
343 } catch (RemoteApiException e) {
344 IdeaHelper.handleRemoteApiException(project, e);
345 } catch (ServerPasswordNotProvidedException e) {
346 IdeaHelper.handleMissingPassword(e);
347 }
348 }
349
350 @Override
351 public void aboutToUpdateVersionedComment(final ReviewData review, final CrucibleFileInfo file,
352 final VersionedComment comment) {
353 try {
354 facade.updateComment(review.getServer(), review.getPermId(), comment);
355 eventBroker.trigger(new VersionedCommentUpdated(this, review, file, comment));
356 } catch (RemoteApiException e) {
357 IdeaHelper.handleRemoteApiException(project, e);
358 } catch (ServerPasswordNotProvidedException e) {
359 IdeaHelper.handleMissingPassword(e);
360 }
361 }
362
363 @Override
364 public void aboutToUpdateGeneralComment(final ReviewData review, final GeneralComment comment) {
365 try {
366 facade.updateComment(review.getServer(), review.getPermId(), comment);
367 eventBroker.trigger(new GeneralCommentUpdated(this, review, comment));
368 } catch (RemoteApiException e) {
369 IdeaHelper.handleRemoteApiException(project, e);
370 } catch (ServerPasswordNotProvidedException e) {
371 IdeaHelper.handleMissingPassword(e);
372 }
373 }
374
375 @Override
376 public void aboutToRemoveComment(final ReviewData review, final Comment comment) {
377 try {
378 facade.removeComment(review.getServer(), review.getPermId(), comment);
379 eventBroker.trigger(new CommentRemoved(this, review, comment));
380 } catch (RemoteApiException e) {
381 IdeaHelper.handleRemoteApiException(project, e);
382 } catch (ServerPasswordNotProvidedException e) {
383 IdeaHelper.handleMissingPassword(e);
384 }
385 }
386 }
387
388 }