View Javadoc

1   /**
2    * Copyright (C) 2008 Atlassian
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  
17  package com.atlassian.theplugin.idea;
18  
19  import com.atlassian.theplugin.commons.crucible.ValueNotYetInitialized;
20  import com.atlassian.theplugin.commons.crucible.api.model.Comment;
21  import com.atlassian.theplugin.commons.crucible.api.model.CrucibleFileInfo;
22  import com.atlassian.theplugin.commons.crucible.api.model.GeneralComment;
23  import com.atlassian.theplugin.commons.crucible.api.model.VersionedComment;
24  import com.atlassian.theplugin.idea.crucible.CrucibleFilteredModelProvider;
25  import com.atlassian.theplugin.idea.crucible.CrucibleHelper;
26  import com.atlassian.theplugin.idea.crucible.ReviewData;
27  import com.atlassian.theplugin.idea.crucible.comments.CrucibleReviewActionListener;
28  import com.atlassian.theplugin.idea.crucible.events.*;
29  import com.atlassian.theplugin.idea.ui.AtlassianToolbar;
30  import com.atlassian.theplugin.idea.ui.tree.*;
31  import com.atlassian.theplugin.idea.ui.tree.comment.FileNameNode;
32  import com.atlassian.theplugin.idea.ui.tree.comment.GeneralCommentTreeNode;
33  import com.atlassian.theplugin.idea.ui.tree.comment.GeneralSectionNode;
34  import com.atlassian.theplugin.idea.ui.tree.comment.VersionedCommentTreeNode;
35  import com.atlassian.theplugin.idea.ui.tree.file.FolderNode;
36  import com.atlassian.theplugin.util.PluginUtil;
37  import com.intellij.openapi.actionSystem.ActionGroup;
38  import com.intellij.openapi.actionSystem.ActionManager;
39  import com.intellij.openapi.application.ApplicationManager;
40  import com.intellij.openapi.editor.Editor;
41  import com.intellij.openapi.project.Project;
42  import com.intellij.util.ui.UIUtil;
43  
44  import javax.swing.*;
45  import javax.swing.tree.TreePath;
46  import java.awt.*;
47  import java.awt.event.MouseAdapter;
48  import java.awt.event.MouseEvent;
49  import java.util.List;
50  
51  /**
52   * Created by IntelliJ IDEA.
53   * User: lguminski
54   * Date: Jul 15, 2008
55   * Time: 1:26:58 PM
56   * To change this template use File | Settings | File Templates.
57   */
58  public class CommentTreePanel extends JPanel {
59  	private CrucibleReviewActionListener crucibleAgent = new MyCrucibleReviewActionListener();
60  	private JScrollPane commentScroll;
61  	private ProgressAnimationProvider progressAnimation = new ProgressAnimationProvider();
62  	private CommentTree commentTree = new CommentTree();
63  
64  	private AtlassianTreeModel fullModel;
65  
66  	public static final AtlassianTreeNode ROOT = new FolderNode("/", AtlassianClickAction.EMPTY_ACTION);
67  	private Project project;
68  	private static final String TOOLBAR_ID = "ThePlugin.Crucible.Comment.ToolBar";
69  	public static final String MENU_PLACE = "menu comments";
70  	private static final String TOOLBAR_PLACE = "toolbar comments";
71  	private CrucibleFilteredModelProvider.FILTER filter;
72  
73  	public CommentTreePanel(Project project, CrucibleFilteredModelProvider.FILTER filter) {
74  		this.project = project;
75  		this.filter = filter;
76  		IdeaHelper.getReviewActionEventBroker(project).registerListener(crucibleAgent);
77  		initialize();
78  	}
79  
80  	private void initialize() {
81  		setLayout(new BorderLayout());
82  		setBackground(UIUtil.getTreeTextBackground());
83  		progressAnimation.configure(this, commentScroll, BorderLayout.CENTER);
84  		commentScroll = new JScrollPane();
85  		add(AtlassianToolbar.createToolbar(TOOLBAR_PLACE, TOOLBAR_ID), BorderLayout.NORTH);
86  		add(commentScroll, BorderLayout.CENTER);
87  	}
88  
89  	private void addGeneralCommentTree(AtlassianTreeNode root, final ReviewData review,
90  			GeneralComment generalComment, int depth) {
91  		if (generalComment.isDeleted()) {
92  			return;
93  		}
94  		GeneralCommentTreeNode commentNode
95  				= new GeneralCommentTreeNode(review, generalComment, new GeneralCommentClickAction());
96  		root.addNode(commentNode);
97  		for (GeneralComment comment : generalComment.getReplies()) {
98  			addGeneralCommentTree(commentNode, review, comment, depth + 1);
99  		}
100 
101 
102 	}
103 
104 	private void addVersionedCommentTree(AtlassianTreeNode root, final ReviewData review,
105 			final CrucibleFileInfo file, VersionedComment versionedComment, int depth) {
106 		if (versionedComment.isDeleted()) {
107 			return;
108 		}
109 		VersionedCommentTreeNode commentNode = new VersionedCommentTreeNode(review, file, versionedComment,
110 				new VersionedCommentClickAction());
111 		root.addNode(commentNode);
112 		for (VersionedComment comment : versionedComment.getReplies()) {
113 			addVersionedCommentTree(commentNode, review, file, comment, depth + 1);
114 		}
115 	}
116 
117 
118 	private AtlassianTreeModel createTreeModel(final ReviewData review) {
119 		ROOT.removeAllChildren();
120 		AtlassianTreeModel model = new AtlassianTreeModel(ROOT);
121 
122 		List<GeneralComment> generalComments;
123 		try {
124 			generalComments = review.getGeneralComments();
125 			AtlassianTreeNode generalNode = new GeneralSectionNode(review, new AtlassianClickAction() {
126 				public void execute(final AtlassianTreeNode node, final int noOfClicks) {
127 					switch (noOfClicks) {
128 						case 1:
129 							GeneralSectionNode anode = (GeneralSectionNode) node;
130 							IdeaHelper.getReviewActionEventBroker(project).trigger(
131 									new FocusOnReviewEvent(crucibleAgent, anode.getReview()));
132 							break;
133 						default:
134 							// do nothing
135 					}
136 
137 				}
138 			});
139 			ROOT.addNode(generalNode);
140 			for (GeneralComment comment : generalComments) {
141 				addGeneralCommentTree(generalNode, review, comment, 0);
142 			}
143 			for (CrucibleFileInfo file : review.getFiles()) {
144 				AtlassianTreeNode fileNode = new FileNameNode(review, file, new AtlassianClickAction() {
145 					public void execute(final AtlassianTreeNode node, final int noOfClicks) {
146 						switch (noOfClicks) {
147 							case 1:
148 								FileNameNode anode = (FileNameNode) node;
149 								IdeaHelper.getReviewActionEventBroker(project).trigger(
150 										new FocusOnFileEvent(crucibleAgent, anode.getReview(), anode.getFile()));
151 								break;
152 							default:
153 								// do nothing
154 						}
155 
156 					}
157 				});
158 				ROOT.addNode(fileNode);
159 				for (VersionedComment comment : file.getVersionedComments()) {
160 					addVersionedCommentTree(fileNode, review, file, comment, 0);
161 				}
162 
163 			}
164 		} catch (ValueNotYetInitialized valueNotYetInitialized) {
165 			PluginUtil.getLogger().error(valueNotYetInitialized.getMessage());
166 		}
167 		return model;
168 	}
169 
170 	public CommentTree getCommentTree() {
171 		return commentTree;
172 	}
173 
174 	public void filterTreeNodes(CrucibleFilteredModelProvider.FILTER aFilter) {
175 		this.filter = aFilter;
176 		commentTree.setModel(fullModel.getFilteredModel(getFilter(aFilter)));
177 		refreshTree();
178 	}
179 
180 	private Filter getFilter(final CrucibleFilteredModelProvider.FILTER aFilter) {
181 		switch (aFilter) {
182 			case FILES_ALL:
183 				return Filter.ALL;
184 			case FILES_WITH_COMMENTS_ONLY:
185 				return new Filter() {
186 					@Override
187 					public boolean isValid(final AtlassianTreeNode node) {
188 						if (node instanceof FileNameNode) {
189 							FileNameNode anode = (FileNameNode) node;
190 							try {
191 								return anode.getFile().getNumberOfComments() > 0;
192 							} catch (ValueNotYetInitialized valueNotYetInitialized) {
193 								return false;
194 							}
195 						}
196 						return true;
197 					}
198 				};
199 			default:
200 				throw new IllegalStateException("Unknow filtering requested (" + aFilter.toString() + ")");
201 		}
202 	}
203 
204 	private void refreshTree() {
205 		commentTree.setRootVisible(false);
206 		commentTree.expandAll();
207 		commentScroll.setViewportView(commentTree);
208 		commentTree.initializeUI();
209 		commentTree.setVisible(true);
210 		commentTree.setEnabled(true);
211 		commentTree.revalidate();
212 		commentTree.repaint();
213 		commentTree.addMouseListener(new PopupMouseAdapter());
214 	}
215 
216 	private class MyCrucibleReviewActionListener extends CrucibleReviewActionListener {
217 
218 		@Override
219 		public void showReview(final ReviewData review) {
220 			EventQueue.invokeLater(new Runnable() {
221 				public void run() {
222 					commentTree.setVisible(false);
223 					fullModel = createTreeModel(review);
224 					commentTree = new CommentTree(fullModel.getFilteredModel(getFilter(filter)));
225 					refreshTree();
226 				}
227 			});
228 		}
229 
230 		@Override
231 		public void focusOnGeneralComments(final ReviewData review) {
232 			EventQueue.invokeLater(new Runnable() {
233 				public void run() {
234 					AtlassianTreeModel model = (AtlassianTreeModel) commentTree.getModel();
235 					AtlassianTreeNode node = model.locateNode(new SearchGeneralSectionAlgorithm());
236 					commentTree.focusOnNode(node);
237 				}
238 			});
239 		}
240 
241 		@Override
242 		public void createdGeneralComment(final ReviewData review, final GeneralComment comment) {
243 			EventQueue.invokeLater(new Runnable() {
244 				public void run() {
245 					AtlassianTreeNode newCommentNode
246 							= new GeneralCommentTreeNode(review, comment, new GeneralCommentClickAction());
247 
248 					SearchGeneralCommentAlgorithm replacementLocator = new SearchGeneralCommentAlgorithm(review, comment);
249 					AtlassianTreeNode changedNode = replaceNode(replacementLocator,
250 							newCommentNode);
251 					if (changedNode ==  null) {
252 						SearchGeneralSectionAlgorithm parentLocator = new SearchGeneralSectionAlgorithm();
253 						changedNode = addNewNode(parentLocator, newCommentNode);
254 					}
255 					addReplyNodes(review, newCommentNode, comment);
256 					refreshNode(changedNode);
257 				}
258 			}
259 			);
260 		}
261 
262 		private void refreshNode(final AtlassianTreeNode node) {
263 			if (node == null) {
264 				return;
265 			}
266 			commentTree.expandFromNode(node);
267 			((AtlassianTreeModel) commentTree.getModel()).nodeChanged(node.getParent());
268 		}
269 
270 		@Override
271 		public void createdGeneralCommentReply(final ReviewData review, final GeneralComment parentComment,
272 				final GeneralComment comment) {
273 			EventQueue.invokeLater(new Runnable() {
274 				public void run() {
275 					GeneralCommentTreeNode newCommentNode = new GeneralCommentTreeNode(review, comment,
276 							AtlassianClickAction.EMPTY_ACTION);
277 					AtlassianTreeNode changedNode = replaceNode(new SearchGeneralCommentAlgorithm(review, comment),
278 							newCommentNode);
279 					if (changedNode == null) {
280 						changedNode = addNewNode(new SearchGeneralCommentAlgorithm(review, parentComment), newCommentNode);
281 					}
282 					addReplyNodes(review, newCommentNode, comment);
283 					refreshNode(changedNode);
284 				}
285 			}
286 			);
287 		}
288 
289 		@Override
290 		public void createdVersionedComment(final ReviewData review, final CrucibleFileInfo file,
291 				final VersionedComment comment) {
292 			EventQueue.invokeLater(new Runnable() {
293 				public void run() {
294 
295 					AtlassianTreeNode newCommentNode
296 							= new VersionedCommentTreeNode(review, file, comment, new VersionedCommentClickAction());
297 
298 					AtlassianTreeNode changedNode = replaceNode(new SearchVersionedCommentAlgorithm(review, file, comment),
299 							newCommentNode);
300 					if (changedNode == null) {
301 						changedNode = addNewNode(new NodeSearchAlgorithm() {
302 							@Override
303 							public boolean check(AtlassianTreeNode node) {
304 								if (node instanceof FileNameNode) {
305 									FileNameNode vnode = (FileNameNode) node;
306 									if (vnode.getReview().getPermId().equals(review.getPermId())
307 											&& vnode.getFile().getPermId().equals(file.getPermId())) {
308 										return true;
309 									}
310 								}
311 								return false;
312 							}
313 						}, newCommentNode);
314 					}
315 					addReplyNodes(review, file, newCommentNode, comment);
316 					refreshNode(changedNode);
317 
318 					Editor editor = CrucibleHelper.getEditorForCrucibleFile(review, file);
319 					if (editor != null) {
320 						CrucibleHelper.openFileOnComment(project, review, file, comment);
321 					}
322 				}
323 			});
324 		}
325 
326 		@Override
327 		public void createdVersionedCommentReply(final ReviewData review, final CrucibleFileInfo file,
328 				final VersionedComment parentComment, final VersionedComment comment) {
329 
330 			EventQueue.invokeLater(new Runnable() {
331 				public void run() {
332 					VersionedCommentTreeNode newCommentNode = new VersionedCommentTreeNode(review, file, comment,
333 							AtlassianClickAction.EMPTY_ACTION);
334 					AtlassianTreeNode changedNode = replaceNode(new SearchVersionedCommentAlgorithm(review, file, comment),
335 							newCommentNode);
336 					if (changedNode == null) {
337 						changedNode = addNewNode(new SearchVersionedCommentAlgorithm(review, file, parentComment),
338 								newCommentNode);
339 					}
340 					addReplyNodes(review, file, newCommentNode, comment);
341 					refreshNode(changedNode);
342 				}
343 			});
344 		}
345 
346 		@Override
347 		public void updatedVersionedComment(final ReviewData review, final CrucibleFileInfo file,
348 				final VersionedComment comment) {
349 			EventQueue.invokeLater(new Runnable() {
350 				public void run() {
351 					VersionedCommentTreeNode newCommentNode = new VersionedCommentTreeNode(review, file, comment,
352 							AtlassianClickAction.EMPTY_ACTION);
353 					AtlassianTreeNode changedNode = replaceNode(new SearchVersionedCommentAlgorithm(review, file, comment),
354 							newCommentNode);
355 					refreshNode(changedNode);
356 				}
357 			});
358 		}
359 
360 		@Override
361 		public void updatedGeneralComment(final ReviewData review, final GeneralComment comment) {
362 			EventQueue.invokeLater(new Runnable() {
363 				public void run() {
364 					GeneralCommentTreeNode newCommentNode = new GeneralCommentTreeNode(review, comment,
365 							AtlassianClickAction.EMPTY_ACTION);
366 					AtlassianTreeNode changedNode = replaceNode(new SearchGeneralCommentAlgorithm(review, comment),
367 							newCommentNode);
368 					refreshNode(changedNode);
369 				}
370 			});
371 		}
372 
373 		@Override
374 		public void removedComment(final ReviewData review, final Comment comment) {
375 			EventQueue.invokeLater(new Runnable() {
376 				public void run() {
377 					removeNode(new NodeSearchAlgorithm() {
378 						@Override
379 						public boolean check(AtlassianTreeNode node) {
380 							if (node instanceof VersionedCommentTreeNode) {
381 								VersionedCommentTreeNode vnode = (VersionedCommentTreeNode) node;
382 								if (vnode.getReview().getPermId().equals(review.getPermId())
383 										&& vnode.getComment().getPermId().equals(comment.getPermId())) {
384 									return true;
385 								}
386 							} else if (node instanceof GeneralCommentTreeNode) {
387 								GeneralCommentTreeNode vnode = (GeneralCommentTreeNode) node;
388 								if (vnode.getReview().getPermId().equals(review.getPermId())
389 										&& vnode.getComment().getPermId().equals(comment.getPermId())) {
390 									return true;
391 								}
392 							}
393 							return false;
394 						}
395 					});
396 				}
397 			}
398 			);
399 
400 		}
401 
402 		@Override
403 		public void publishedGeneralComment(final ReviewData review, final GeneralComment comment) {
404 			EventQueue.invokeLater(new Runnable() {
405 				public void run() {
406 					GeneralCommentTreeNode newCommentNode = new GeneralCommentTreeNode(review, comment,
407 							AtlassianClickAction.EMPTY_ACTION);
408 					AtlassianTreeNode changedNode = replaceNode(new SearchGeneralCommentAlgorithm(review, comment),
409 							newCommentNode);
410 					refreshNode(changedNode);
411 				}
412 			}
413 			);
414 		}
415 
416 		@Override
417 		public void publishedVersionedComment(final ReviewData review, final CrucibleFileInfo file,
418 				final VersionedComment comment) {
419 			EventQueue.invokeLater(new Runnable() {
420 				public void run() {
421 					VersionedCommentTreeNode newCommentNode = new VersionedCommentTreeNode(review, file, comment,
422 							AtlassianClickAction.EMPTY_ACTION);
423 					AtlassianTreeNode changedNode = replaceNode(new SearchVersionedCommentAlgorithm(review, file, comment),
424 							newCommentNode);
425 					refreshNode(changedNode);
426 				}
427 
428 			}
429 			);
430 		}
431 
432 		public void commentsChanged(final ReviewData review, final CrucibleFileInfo file) {
433 			ApplicationManager.getApplication().invokeLater(new Runnable() {
434 				public void run() {
435 				}
436 			});
437 		}
438 
439 		@Override
440 		public void focusOnFileComments(final ReviewData review, final CrucibleFileInfo file) {
441 			EventQueue.invokeLater(new Runnable() {
442 				public void run() {
443 					AtlassianTreeModel model = (AtlassianTreeModel) commentTree.getModel();
444 					AtlassianTreeNode node = model.locateNode(new NodeSearchAlgorithm() {
445 						public boolean check(AtlassianTreeNode node) {
446 							if (node instanceof FileNameNode) {
447 								FileNameNode vnode = (FileNameNode) node;
448 								if (vnode.getReview().equals(review)
449 										&& vnode.getFile().equals(file)) {
450 									return true;
451 								}
452 							}
453 							return false;
454 						}
455 					});
456 					commentTree.focusOnNode(node);
457 				}
458 			});
459 		}
460 
461 		private void removeNode(final NodeSearchAlgorithm nodeLocator) {
462 			AtlassianTreeModel model = (AtlassianTreeModel) commentTree.getModel();
463 			AtlassianTreeNode node = model.locateNode(nodeLocator);
464 			if (node != null) {
465 				model.removeNodeFromParent(node);
466 			}
467 		}
468 
469 		private AtlassianTreeNode replaceNode(final NodeSearchAlgorithm nodeLocator,
470 				final AtlassianTreeNode newNode) {
471 			AtlassianTreeModel model = (AtlassianTreeModel) commentTree.getModel();
472 			AtlassianTreeNode node = model.locateNode(nodeLocator);
473 			if (node != null) {
474 				AtlassianTreeNode parent = (AtlassianTreeNode) node.getParent();
475 				parent.remove(node);
476 				parent.addNode(newNode);
477 				return newNode;
478 			}
479 			return null;
480 		}
481 
482 		private void addReplyNodes(final ReviewData review, final AtlassianTreeNode parentNode, final GeneralComment comment) {
483 			for (GeneralComment reply : comment.getReplies()) {
484 				GeneralCommentTreeNode childNode = new GeneralCommentTreeNode(review, reply, AtlassianClickAction.EMPTY_ACTION);
485 				addNewNode(parentNode, childNode);
486 				addReplyNodes(review, childNode, reply);
487 			}
488 		}
489 
490 		private void addReplyNodes(final ReviewData review, final CrucibleFileInfo file, final AtlassianTreeNode parentNode,
491 				final VersionedComment comment) {
492 			for (VersionedComment reply : comment.getReplies()) {
493 				VersionedCommentTreeNode childNode = new VersionedCommentTreeNode(review, file, reply,
494 						AtlassianClickAction.EMPTY_ACTION);
495 				addNewNode(parentNode, childNode);
496 				addReplyNodes(review, file, childNode, reply);
497 			}
498 
499 		}
500 
501 		private AtlassianTreeNode addNewNode(NodeSearchAlgorithm parentLocator, AtlassianTreeNode newCommentNode) {
502 			AtlassianTreeModel model = (AtlassianTreeModel) commentTree.getModel();
503 			return addNewNode(model.locateNode(parentLocator), newCommentNode);
504 		}
505 
506 		private AtlassianTreeNode addNewNode(AtlassianTreeNode parentNode, AtlassianTreeNode newCommentNode) {
507 			if (parentNode != null) {
508 				AtlassianTreeModel model = (AtlassianTreeModel) commentTree.getModel();
509 				model.insertNodeInto(newCommentNode, parentNode, parentNode.getChildCount());
510 				parentNode.addNode(newCommentNode);
511 				commentTree.expandPath(new TreePath(newCommentNode.getPath()));
512 				commentTree.expandPath(new TreePath(parentNode.getPath()));
513 				commentTree.focusOnNode(newCommentNode);
514 				return parentNode;
515 			}
516 			return null;
517 		}
518 
519 		private class SearchVersionedCommentAlgorithm extends NodeSearchAlgorithm {
520 			private final ReviewData review;
521 			private final CrucibleFileInfo file;
522 			private final VersionedComment parentComment;
523 
524 			public SearchVersionedCommentAlgorithm(final ReviewData review, final CrucibleFileInfo file,
525 					final VersionedComment parentComment) {
526 				this.review = review;
527 				this.file = file;
528 				this.parentComment = parentComment;
529 			}
530 
531 			@Override
532 			public boolean check(AtlassianTreeNode node) {
533 				if (node instanceof VersionedCommentTreeNode) {
534 					VersionedCommentTreeNode vnode = (VersionedCommentTreeNode) node;
535 					if (vnode.getReview().getPermId().equals(review.getPermId())
536 							&& vnode.getFile().getPermId().equals(file.getPermId())
537 							&& vnode.getComment().getPermId().equals(parentComment.getPermId())) {
538 						return true;
539 					}
540 				}
541 				return false;
542 			}
543 		}
544 
545 		private class SearchGeneralCommentAlgorithm extends NodeSearchAlgorithm {
546 			private final ReviewData review;
547 			private final GeneralComment comment;
548 
549 			public SearchGeneralCommentAlgorithm(final ReviewData review, final GeneralComment comment) {
550 				this.review = review;
551 				this.comment = comment;
552 			}
553 
554 			@Override
555 			public boolean check(AtlassianTreeNode node) {
556 				if (node instanceof GeneralCommentTreeNode) {
557 					GeneralCommentTreeNode vnode = (GeneralCommentTreeNode) node;
558 					if (vnode.getReview().getPermId().equals(review.getPermId())
559 							&& vnode.getComment().getPermId().equals(comment.getPermId())) {
560 						return true;
561 					}
562 				}
563 				return false;
564 			}
565 		}
566 
567 		private class SearchGeneralSectionAlgorithm extends NodeSearchAlgorithm {
568 			@Override
569 			public boolean check(AtlassianTreeNode node) {
570 				return node instanceof GeneralSectionNode;
571 			}
572 		}
573 	}
574 
575 	private class PopupMouseAdapter extends MouseAdapter {
576 
577 		@Override
578 		public void mousePressed(MouseEvent e) {
579 			processPopup(e);
580 		}
581 
582 		@Override
583 		public void mouseReleased(MouseEvent e) {
584 			processPopup(e);
585 		}
586 
587 		public void processPopup(MouseEvent e) {
588 			if (!e.isPopupTrigger()) {
589 				return;
590 			}
591 
592 			final CommentTree tree = (CommentTree) e.getComponent();
593 
594 			TreePath path = tree.getPathForLocation(e.getX(), e.getY());
595 			if (path == null) {
596 				return;
597 			}
598 			tree.setSelectionPath(path);
599 			Object o = path.getLastPathComponent();
600 			if (!(o instanceof AtlassianTreeNode)) {
601 				return;
602 			}
603 			ActionManager aManager = ActionManager.getInstance();
604 			ActionGroup menu = (ActionGroup) aManager.getAction(TOOLBAR_ID);
605 			if (menu == null) {
606 				return;
607 			}
608 			aManager.createActionPopupMenu(MENU_PLACE, menu).getComponent().show(e.getComponent(), e.getX(), e.getY());
609 		}
610 	}
611 
612 	private class VersionedCommentClickAction implements AtlassianClickAction {
613 		public void execute(final AtlassianTreeNode node, final int noOfClicks) {
614 			VersionedCommentTreeNode anode = (VersionedCommentTreeNode) node;
615 			CrucibleEvent event;
616 			switch (noOfClicks) {
617 				case 1:
618 					if (anode.getComment().isFromLineInfo()
619 							|| anode.getComment().isToLineInfo()) {
620 						event = new FocusOnLineCommentEvent(crucibleAgent,
621 								anode.getReview(),
622 								anode.getFile(), anode.getComment(), false);
623 					} else {
624 						event = new FocusOnVersionedCommentEvent(crucibleAgent,
625 								anode.getReview(),
626 								anode.getFile(), anode.getComment());
627 					}
628 					IdeaHelper.getReviewActionEventBroker(project).trigger(
629 							event);
630 					break;
631 				case 2:
632 					if (anode.getComment().isFromLineInfo()
633 							|| anode.getComment().isToLineInfo()) {
634 						event = new FocusOnLineCommentEvent(crucibleAgent,
635 								anode.getReview(),
636 								anode.getFile(), anode.getComment(), true);
637 					} else {
638 						event = new FocusOnVersionedCommentEvent(crucibleAgent,
639 								anode.getReview(),
640 								anode.getFile(), anode.getComment());
641 					}
642 					IdeaHelper.getReviewActionEventBroker(project).trigger(
643 							event);
644 					break;
645 				default:
646 					// do nothing
647 			}
648 
649 		}
650 	}
651 
652 	private class GeneralCommentClickAction implements AtlassianClickAction {
653 		public void execute(final AtlassianTreeNode node, final int noOfClicks) {
654 			switch (noOfClicks) {
655 				case 1:
656 					GeneralCommentTreeNode anode = (GeneralCommentTreeNode) node;
657 					IdeaHelper.getReviewActionEventBroker(project).trigger(
658 							new FocusOnReviewEvent(crucibleAgent, anode.getReview()));
659 					break;
660 				default:
661 					// do nothing
662 			}
663 
664 		}
665 	}
666 }