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.crucible;
18  
19  
20  import com.atlassian.theplugin.commons.bamboo.StausIconBambooListener;
21  import com.atlassian.theplugin.commons.crucible.CrucibleFiltersBean;
22  import com.atlassian.theplugin.commons.crucible.CrucibleVersion;
23  import com.atlassian.theplugin.commons.crucible.api.model.CustomFilterBean;
24  import com.atlassian.theplugin.commons.crucible.api.model.PermId;
25  import com.atlassian.theplugin.commons.crucible.api.model.PredefinedFilter;
26  import com.atlassian.theplugin.commons.util.Logger;
27  import com.atlassian.theplugin.configuration.ProjectConfigurationBean;
28  import com.atlassian.theplugin.idea.CrucibleReviewWindow;
29  import com.atlassian.theplugin.idea.IdeaHelper;
30  import com.atlassian.theplugin.idea.ProgressAnimationProvider;
31  import com.atlassian.theplugin.idea.VcsIdeaHelper;
32  import com.atlassian.theplugin.idea.bamboo.ToolWindowBambooContent;
33  import com.atlassian.theplugin.idea.crucible.comments.CrucibleReviewActionListener;
34  import com.atlassian.theplugin.idea.crucible.events.ShowReviewEvent;
35  import com.atlassian.theplugin.idea.ui.AtlassianTableView;
36  import com.atlassian.theplugin.idea.ui.CollapsibleTable;
37  import com.atlassian.theplugin.idea.ui.TableColumnProvider;
38  import com.atlassian.theplugin.idea.ui.TableItemSelectedListener;
39  import com.atlassian.theplugin.util.PluginUtil;
40  import com.intellij.openapi.actionSystem.ActionGroup;
41  import com.intellij.openapi.actionSystem.ActionManager;
42  import com.intellij.openapi.actionSystem.ActionToolbar;
43  import com.intellij.openapi.project.Project;
44  import com.intellij.openapi.ui.VerticalFlowLayout;
45  import com.intellij.openapi.ui.Messages;
46  import com.intellij.openapi.util.Key;
47  import com.intellij.ui.table.TableView;
48  import com.intellij.util.ui.UIUtil;
49  import thirdparty.javaworld.ClasspathHTMLEditorKit;
50  
51  import javax.swing.*;
52  import java.awt.*;
53  import java.util.HashMap;
54  import java.util.HashSet;
55  import java.util.List;
56  import java.util.Map;
57  import java.util.Set;
58  
59  public class CrucibleTableToolWindowPanel extends JPanel implements CrucibleStatusListener,
60  		TableItemSelectedListener<ReviewData> {
61  	public static final String PLACE_PREFIX = CrucibleTableToolWindowPanel.class.getSimpleName();
62  	private static final Key<CrucibleTableToolWindowPanel> WINDOW_PROJECT_KEY
63  			= Key.create(CrucibleTableToolWindowPanel.class.getName());
64  	private Project project;
65  	private transient ActionToolbar filterEditToolbar;
66  	private TableColumnProvider columnProvider;
67  
68  	private CrucibleCustomFilterPanel crucibleCustomFilterPanel;
69  
70  	private ProjectConfigurationBean projectCfg;
71  	private final CrucibleStatusChecker crucibleStatusChecker;
72  	private JPanel toolBarPanel;
73  	private JPanel dataPanelsHolder;
74  	private ToolWindowBambooContent editorPane;
75  	private CrucibleReviewActionListener listener = new CrucibleReviewActionListener();
76  
77  	public CrucibleFiltersBean getFilters() {
78  		return filters;
79  	}
80  
81  	private transient CrucibleFiltersBean filters;
82  
83  	protected JScrollPane tablePane;
84  	protected static final Dimension ED_PANE_MINE_SIZE = new Dimension(200, 200);
85  	protected ProgressAnimationProvider progressAnimation = new ProgressAnimationProvider();
86  
87  	protected TableColumnProvider tableColumnProvider = new CrucibleTableColumnProviderImpl();
88  
89  	private ReviewData selectedItem;
90  
91  	private Map<PredefinedFilter, CollapsibleTable> tables = new HashMap<PredefinedFilter, CollapsibleTable>();
92  	private Map<String, CollapsibleTable> customTables = new HashMap<String, CollapsibleTable>();
93  
94  	private CrucibleVersion crucibleVersion = CrucibleVersion.UNKNOWN;
95  
96  	protected String getInitialMessage() {
97  
98  		return "Waiting for Crucible review info.";
99  	}
100 
101 	protected String getToolbarActionGroup() {
102 		return "ThePlugin.CrucibleToolWindowToolBar";
103 	}
104 
105 	protected String getPopupActionGroup() {
106 		return "ThePlugin.Crucible.ReviewPopupMenu";
107 	}
108 
109 	protected TableColumnProvider getTableColumnProvider() {
110 		if (columnProvider == null) {
111 			columnProvider = new CrucibleTableColumnProviderImpl();
112 		}
113 		return columnProvider;
114 	}
115 
116 	public void applyAdvancedFilter() {
117 		if (crucibleCustomFilterPanel.getFilter() != null) {
118 			CustomFilterBean filter = crucibleCustomFilterPanel.getFilter();
119 
120 			filters.getManualFilter().put(filter.getTitle(), filter);
121 			projectCfg.getCrucibleConfiguration().getCrucibleFilters().getManualFilter().put(filter.getTitle(), filter);
122 			refreshReviews(crucibleStatusChecker);
123 		}
124 		hideCrucibleCustomFilter();
125 	}
126 
127 	public void cancelAdvancedFilter() {
128 		hideCrucibleCustomFilter();
129 	}
130 
131 
132 	public void clearAdvancedFilter() {
133 	}
134 
135 //	public static CrucibleTableToolWindowPanel getInstance(com.intellij.openapi.project.Project project,
136 //			ProjectConfigurationBean projectConfigurationBean) {
137 //
138 //		CrucibleTableToolWindowPanel window = project.getUserData(WINDOW_PROJECT_KEY);
139 //
140 //		if (window == null) {
141 //			window = new CrucibleTableToolWindowPanel(project, projectConfigurationBean);
142 //			project.putUserData(WINDOW_PROJECT_KEY, window);
143 //			CrucibleReviewWindow.getInstance(project);
144 //		}
145 //		return window;
146 //	}
147 
148 	public CrucibleTableToolWindowPanel(Project project, ProjectConfigurationBean projectConfigurationBean,
149 			CrucibleStatusChecker crucibleStatusChecker) {
150 		super(new BorderLayout());
151 		this.project = project;
152 		this.projectCfg = projectConfigurationBean;
153 		this.crucibleStatusChecker = crucibleStatusChecker;
154 
155 		setBackground(UIUtil.getTreeTextBackground());
156 
157 		toolBarPanel = new JPanel(new BorderLayout());
158 
159 		createFilterToolBar();
160 		add(toolBarPanel, BorderLayout.NORTH);
161 
162 		editorPane = new ToolWindowBambooContent();
163 		editorPane.setEditorKit(new ClasspathHTMLEditorKit());
164 		JScrollPane pane = setupPane(editorPane, wrapBody(getInitialMessage()));
165 		editorPane.setMinimumSize(ED_PANE_MINE_SIZE);
166 		add(pane, BorderLayout.SOUTH);
167 
168 		dataPanelsHolder = new JPanel();
169 		dataPanelsHolder.setLayout(new VerticalFlowLayout());
170 
171 		tablePane = new JScrollPane(dataPanelsHolder,
172 				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
173 		tablePane.setWheelScrollingEnabled(true);
174 
175 		add(tablePane, BorderLayout.CENTER);
176 
177 		progressAnimation.configure(this, tablePane, BorderLayout.CENTER);
178 
179 		createFilterEditToolBar(getPlaceName(), "ThePlugin.Crucible.FilterEditToolBar");
180 		this.crucibleCustomFilterPanel = new CrucibleCustomFilterPanel(project, IdeaHelper.getCfgManager());
181 		filters = projectCfg.getCrucibleConfiguration().getCrucibleFilters();
182 		if (filters.getReadStored() == null) {
183 			filters.getPredefinedFilters()[PredefinedFilter.ToReview.ordinal()] = true;
184 			filters.getPredefinedFilters()[PredefinedFilter.OutForReview.ordinal()] = true;
185 			filters.setReadStored(true);
186 		}
187 	}
188 
189 	private void createFilterToolBar() {
190 		ActionManager actionManager = ActionManager.getInstance();
191 		ActionGroup toolbar = (ActionGroup) actionManager.getAction(getToolbarActionGroup());
192 		ActionToolbar actionToolbar = actionManager.createActionToolbar(
193 				getPlaceName(), toolbar, true);
194 		toolBarPanel.add(actionToolbar.getComponent(), BorderLayout.NORTH);
195 	}
196 
197 	protected void createFilterEditToolBar(String place, String toolbarName) {
198 		ActionManager actionManager = ActionManager.getInstance();
199 		ActionGroup filterEditToolBar = (ActionGroup) actionManager.getAction(toolbarName);
200 		filterEditToolbar = actionManager.createActionToolbar(place,
201 				filterEditToolBar, true);
202 		toolBarPanel.add(filterEditToolbar.getComponent(), BorderLayout.SOUTH);
203 		filterEditToolbarSetVisible(false);
204 	}
205 
206 
207 	private void switchToCrucible16Filter() {
208 		for (int i = 0;
209 			 i < projectCfg.getCrucibleConfiguration().getCrucibleFilters().getPredefinedFilters().length; ++i) {
210 
211 			showPredefinedFilter(
212 					PredefinedFilter.values()[i],
213 					projectCfg.getCrucibleConfiguration().getCrucibleFilters().getPredefinedFilters()[i],
214 					null);
215 		}
216 
217 		for (String s : projectCfg.getCrucibleConfiguration().getCrucibleFilters().getManualFilter().keySet()) {
218 			CustomFilterBean filter = projectCfg.getCrucibleConfiguration()
219 					.getCrucibleFilters().getManualFilter().get(s);
220 
221 			if (filter.isEnabled()) {
222 				this.showCustomFilter(true, null);
223 				break;
224 			}
225 		}
226 	}
227 
228 
229 	private String getPlaceName() {
230 		return PLACE_PREFIX + this.project.getName();
231 	}
232 
233 	/**
234 	 * Method adds or removes CollapsibleTable for given filter type
235 	 *
236 	 * @param filter  predefined filter type
237 	 * @param visible panel added when true, removed when false
238 	 * @param checker
239 	 */
240 	public void showPredefinedFilter(PredefinedFilter filter, boolean visible, CrucibleStatusChecker checker) {
241 		if (visible) {
242 			CollapsibleTable table = new CollapsibleTable(
243 					tableColumnProvider,
244 					projectCfg.getCrucibleConfiguration().getTableConfiguration(),
245 					filter.getFilterName(),
246 					null,
247 					null,
248 					getPlaceName(),
249 					getPopupActionGroup());
250 			table.addItemSelectedListener(this);
251 			TableView.restore(projectCfg.getCrucibleConfiguration().getTableConfiguration(),
252 					table.getTable());
253 
254 			dataPanelsHolder.add(table);
255 			tables.put(filter, table);
256 
257 			refreshReviews(checker);
258 		} else {
259 			if (tables.containsKey(filter)) {
260 				dataPanelsHolder.remove(tables.get(filter));
261 				tables.remove(filter);
262 			}
263 		}
264 		dataPanelsHolder.validate();
265 		tablePane.repaint();
266 	}
267 
268 	protected JScrollPane setupPane(JEditorPane pane, String initialText) {
269 		pane.setText(initialText);
270 		JScrollPane scrollPane = new JScrollPane(pane,
271 				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
272 		scrollPane.setWheelScrollingEnabled(true);
273 		return scrollPane;
274 	}
275 
276 	protected String wrapBody(String s) {
277 		return "<html>" + StausIconBambooListener.BODY_WITH_STYLE + s + "</body></html>";
278 
279 	}
280 
281 	protected void setStatusMessage(String msg) {
282 		setStatusMessage(msg, false);
283 	}
284 
285 	protected void setStatusMessage(String msg, boolean isError) {
286 		editorPane.setBackground(isError ? Color.RED : Color.WHITE);
287 		editorPane.setText(wrapBody("<table width=\"100%\"><tr><td colspan=\"2\">" + msg + "</td></tr></table>"));
288 	}
289 
290 	public ProgressAnimationProvider getProgressAnimation() {
291 		return progressAnimation;
292 	}
293 
294 	public CrucibleVersion getCrucibleVersion() {
295 		return crucibleVersion;
296 	}
297 
298 	public void updateReviews(Map<PredefinedFilter, ReviewNotificationBean> reviews, Map<String,
299 			ReviewNotificationBean> customFilterReviews) {
300 
301 		Set<ReviewData> uniqueReviews = new HashSet<ReviewData>();
302 
303 		this.crucibleVersion = CrucibleVersion.CRUCIBLE_16;
304 		if (tables.isEmpty()) {
305 			switchToCrucible16Filter();
306 		}
307 		for (PredefinedFilter predefinedFilter : reviews.keySet()) {
308 			if (reviews.get(predefinedFilter).getException() == null) {
309 				List<ReviewData> reviewList = reviews.get(predefinedFilter).getReviews();
310 				uniqueReviews.addAll(reviewList);
311 				if (reviewList != null) {
312 					CollapsibleTable table = tables.get(predefinedFilter);
313 					if (table != null) {
314 						table.getListTableModel().setItems(reviewList);
315 						table.getListTableModel().fireTableDataChanged();
316 						table.getTable().revalidate();
317 						table.setEnabled(true);
318 						table.setForeground(UIUtil.getActiveTextColor());
319 						table.setTitle(predefinedFilter.getFilterName() + " (" + reviewList.size() + ")");
320 					}
321 				}
322 			}
323 		}
324 
325 		for (String filterName : customFilterReviews.keySet()) {
326 			if (customFilterReviews.get(filterName).getException() == null) {
327 				List<ReviewData> reviewList = customFilterReviews.get(filterName).getReviews();
328 				uniqueReviews.addAll(reviewList);
329 				if (reviewList != null) {
330 					CollapsibleTable table = customTables.get(filterName);
331 					if (table != null) {
332 						table.getListTableModel().setItems(reviewList);
333 						table.getListTableModel().fireTableDataChanged();
334 						table.getTable().revalidate();
335 						table.setEnabled(true);
336 						table.setForeground(UIUtil.getActiveTextColor());
337 						table.setTitle(filterName + " (" + reviewList.size() + ")");
338 					}
339 				}
340 			}
341 		}
342 
343 
344 		StringBuffer sb = new StringBuffer();
345 		sb.append("Loaded <b>");
346 		sb.append(uniqueReviews.size());
347 		sb.append(" code reviews</b> for defined filters.");
348 		setStatusMessage(sb.toString());
349 
350 		uniqueReviews.clear();
351 	}
352 
353 	public void itemSelected(AtlassianTableView<ReviewData> table, int noClicks) {
354 		if (table.getSelectedObject() != null) {
355 			selectedItem = table.getSelectedObject();
356 
357 			for (CollapsibleTable collapsibleTable : tables.values()) {
358 				if (!collapsibleTable.getTable().equals(table)) {
359 					collapsibleTable.clearSelection();
360 				}
361 			}
362 			for (CollapsibleTable collapsibleTable : customTables.values()) {
363 				if (!collapsibleTable.getTable().equals(table)) {
364 					collapsibleTable.clearSelection();
365 				}
366 			}
367 
368 			if (noClicks == 2) {
369 				if (VcsIdeaHelper.isUnderVcsControl(project)) {
370 					CrucibleReviewWindow.getInstance(project);
371 					IdeaHelper.getReviewActionEventBroker(project).trigger(new ShowReviewEvent(
372 							listener, selectedItem));
373 				} else {
374 
375 					Messages.showInfoMessage(project, CrucibleConstants.CRUCIBLE_MESSAGE_NOT_UNDER_VCS,
376 							CrucibleConstants.CRUCIBLE_TITLE_NOT_UNDER_VCS);
377 				}
378 			}
379 		}
380 	}
381 
382 	public void resetState() {
383 	}
384 
385 	public final void hideCrucibleCustomFilter() {
386 		setScrollPaneViewport(dataPanelsHolder);
387 		filterEditToolbarSetVisible(false);
388 	}
389 
390 	public void collapseAllPanels() {
391 		for (CollapsibleTable collapsibleTable : tables.values()) {
392 			collapsibleTable.collapse();
393 		}
394 		for (CollapsibleTable collapsibleTable : customTables.values()) {
395 			collapsibleTable.collapse();
396 		}
397 	}
398 
399 	public void expandAllPanels() {
400 		for (CollapsibleTable collapsibleTable : tables.values()) {
401 			collapsibleTable.expand();
402 		}
403 		for (CollapsibleTable collapsibleTable : customTables.values()) {
404 			collapsibleTable.expand();
405 		}
406 	}
407 
408 	protected void filterEditToolbarSetVisible(boolean visible) {
409 		filterEditToolbar.getComponent().setVisible(visible);
410 	}
411 
412 	protected void setScrollPaneViewport(JComponent component) {
413 		tablePane.setViewportView(component);
414 	}
415 
416 	public ReviewData getSelectedReview() {
417 		if (selectedItem != null) {
418 			return this.selectedItem;
419 		}
420 		return null;
421 	}
422 
423 	public PermId getSelectedReviewId() {
424 		if (selectedItem != null) {
425 			return this.selectedItem.getPermId();
426 		}
427 		return null;
428 	}
429 
430 	private void showCrucibleCustomFilterPanel() {
431 		filterEditToolbarSetVisible(true);
432 		setScrollPaneViewport(crucibleCustomFilterPanel.$$$getRootComponent$$$());
433 	}
434 
435 	public void showCrucibleCustomFilter() {
436 		if (!projectCfg.getCrucibleConfiguration().getCrucibleFilters().getManualFilter().isEmpty()) {
437 			for (String filterName : projectCfg.getCrucibleConfiguration()
438 					.getCrucibleFilters().getManualFilter().keySet()) {
439 
440 				crucibleCustomFilterPanel.setFilter(
441 						projectCfg.getCrucibleConfiguration().getCrucibleFilters().getManualFilter().get(filterName));
442 				break;
443 			}
444 			showCrucibleCustomFilterPanel();
445 		} else {
446 			addCustomFilter();
447 		}
448 	}
449 
450 	public void addCustomFilter() {
451 		String newName = FilterNameUtil.suggestNewName(
452 				projectCfg.getCrucibleConfiguration().getCrucibleFilters().getManualFilter());
453 		CustomFilterBean newFilter = new CustomFilterBean();
454 		newFilter.setTitle(newName);
455 		crucibleCustomFilterPanel.setFilter(newFilter);
456 		showCrucibleCustomFilterPanel();
457 	}
458 
459 	public void removeCustomFilter() {
460 
461 	}
462 
463 	public void removeItemSelectedListener(TableItemSelectedListener tableListener) {
464 		for (CollapsibleTable table : tables.values()) {
465 			table.removeItemSelectedListener(tableListener);
466 		}
467 
468 
469 		for (CollapsibleTable table : customTables.values()) {
470 			table.removeItemSelectedListener(tableListener);
471 		}
472 	}
473 
474 
475 	public void showCustomFilter(boolean visible, CrucibleStatusChecker checker) {
476 		if (!projectCfg.getCrucibleConfiguration().getCrucibleFilters().getManualFilter().isEmpty()) {
477 			for (String filterName : projectCfg.getCrucibleConfiguration().getCrucibleFilters().getManualFilter().keySet()) {
478 				CustomFilterBean filter
479 						= projectCfg.getCrucibleConfiguration().getCrucibleFilters().getManualFilter().get(filterName);
480 				if (visible) {
481 					if (!customTables.containsKey(filter.getTitle())) {
482 						CollapsibleTable table = new CollapsibleTable(
483 								tableColumnProvider,
484 								projectCfg.getCrucibleConfiguration().getTableConfiguration(),
485 								filter.getTitle(),
486 								null,
487 								null,
488 								getPlaceName(),
489 								getPopupActionGroup());
490 						table.addItemSelectedListener(this);
491 						TableView.restore(projectCfg.getCrucibleConfiguration().getTableConfiguration(),
492 								table.getTable());
493 
494 						dataPanelsHolder.add(table);
495 						customTables.put(filter.getTitle(), table);
496 
497 						refreshReviews(checker);
498 					}
499 				} else {
500 					if (customTables.containsKey(filter.getTitle())) {
501 						dataPanelsHolder.remove(customTables.get(filter.getTitle()));
502 						customTables.remove(filter.getTitle());
503 					}
504 				}
505 			}
506 			dataPanelsHolder.validate();
507 			tablePane.repaint();
508 		}
509 	}
510 
511 	public void refreshReviews(final CrucibleStatusChecker checker) {
512 		if (checker != null) {
513 			if (checker.canSchedule()) {
514 				final ProgressAnimationProvider animator = getProgressAnimation();
515 				final Logger log = PluginUtil.getLogger();
516 
517 				new Thread(new Runnable() {
518 					public void run() {
519 						Thread t = new Thread(checker.newTimerTask(), "Manual Crucible panel refresh (checker)");
520 						animator.startProgressAnimation();
521 						t.start();
522 						try {
523 							t.join();
524 						} catch (InterruptedException e) {
525 							log.warn(e.toString());
526 						} finally {
527 							animator.stopProgressAnimation();
528 						}
529 					}
530 				}, "Manual Crucible panel refresh").start();
531 			}
532 		}
533 	}
534 
535 	public ProjectConfigurationBean getProjectCfg() {
536 		return projectCfg;
537 	}
538 }