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.bamboo.HtmlBambooStatusListenerNotUsed;
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 				// @todo - dirty hack - probably remote api should return new comment info
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 				// @todo - dirty hack - probably remote api should return new comment info
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 }