1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
136
137
138
139
140
141
142
143
144
145
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
235
236
237
238
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 }