1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.atlassian.theplugin.idea.jira.editor;
18
19 import com.atlassian.theplugin.idea.Constants;
20 import com.atlassian.theplugin.idea.IdeaHelper;
21 import com.atlassian.theplugin.idea.jira.CachedIconLoader;
22 import com.atlassian.theplugin.idea.jira.IssueComment;
23 import com.atlassian.theplugin.idea.jira.editor.vfs.MemoryVirtualFile;
24 import com.atlassian.theplugin.jira.JIRAServer;
25 import com.atlassian.theplugin.jira.JIRAServerFacade;
26 import com.atlassian.theplugin.jira.JIRAServerFacadeImpl;
27 import com.atlassian.theplugin.jira.api.JIRAComment;
28 import com.atlassian.theplugin.jira.api.JIRAConstant;
29 import com.atlassian.theplugin.jira.api.JIRAException;
30 import com.atlassian.theplugin.jira.api.JIRAIssue;
31 import com.atlassian.theplugin.util.ColorToHtml;
32 import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
33 import com.intellij.ide.BrowserUtil;
34 import com.intellij.ide.structureView.StructureViewBuilder;
35 import com.intellij.openapi.actionSystem.ActionGroup;
36 import com.intellij.openapi.actionSystem.ActionManager;
37 import com.intellij.openapi.actionSystem.ActionToolbar;
38 import com.intellij.openapi.components.ApplicationComponent;
39 import com.intellij.openapi.fileEditor.*;
40 import com.intellij.openapi.project.Project;
41 import com.intellij.openapi.ui.Messages;
42 import com.intellij.openapi.ui.Splitter;
43 import com.intellij.openapi.ui.VerticalFlowLayout;
44 import com.intellij.openapi.util.IconLoader;
45 import com.intellij.openapi.util.Key;
46 import com.intellij.openapi.vfs.VirtualFile;
47 import com.intellij.ui.HyperlinkLabel;
48 import com.intellij.util.ui.UIUtil;
49 import org.jdom.Element;
50 import org.jetbrains.annotations.NonNls;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
53
54 import javax.swing.*;
55 import javax.swing.border.Border;
56 import javax.swing.event.HyperlinkEvent;
57 import javax.swing.event.HyperlinkListener;
58 import java.awt.*;
59 import java.awt.event.MouseAdapter;
60 import java.awt.event.MouseEvent;
61 import java.beans.PropertyChangeListener;
62 import java.util.ArrayList;
63 import java.util.HashMap;
64 import java.util.List;
65
66
67 public class ThePluginJIRAEditorComponent implements ApplicationComponent, FileEditorProvider {
68
69 @NonNls
70 @NotNull
71 public String getComponentName() {
72 return "ThePluginJIRAEditorComponent";
73 }
74
75 public void initComponent() {
76 }
77
78 public void disposeComponent() {
79 }
80
81 public boolean accept(@NotNull Project project, @NotNull VirtualFile virtualFile) {
82 boolean shouldIAccept = true;
83
84
85
86 if (!(virtualFile instanceof MemoryVirtualFile)) {
87 shouldIAccept = false;
88 }
89 return shouldIAccept;
90 }
91
92 @NotNull
93 public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile virtualFile) {
94 String issueFromFileName = virtualFile.getNameWithoutExtension();
95 JIRAIssue issue = IdeaHelper.getJIRAToolWindowPanel(project).getCurrentIssue();
96 if (issueFromFileName.equals(issue.getKey())) {
97 return new JIRAFileEditor(project, issue);
98 }
99 return new JIRAFileEditor();
100
101 }
102
103 public void disposeEditor(@NotNull FileEditor fileEditor) {
104 }
105
106 @NotNull
107 public FileEditorState readState(@NotNull Element element, @NotNull Project project, @NotNull VirtualFile virtualFile) {
108 return DummyFileEditorState.DUMMY;
109 }
110
111 public void writeState(@NotNull FileEditorState fileEditorState, @NotNull Project project, @NotNull Element element) {
112 }
113
114 @NotNull
115 @NonNls
116 public String getEditorTypeId() {
117 return getComponentName();
118 }
119
120 @NotNull
121 public FileEditorPolicy getPolicy() {
122 return FileEditorPolicy.HIDE_DEFAULT_EDITOR;
123 }
124
125 private static class DummyFileEditorState implements FileEditorState {
126 public static final FileEditorState DUMMY = new DummyFileEditorState();
127
128 public boolean canBeMergedWith(FileEditorState otherState, FileEditorStateLevel level) {
129 return false;
130 }
131 }
132
133 private static class ScrollablePanel extends JPanel implements Scrollable {
134 private static final int A_LOT = 100000;
135
136
137 public Dimension getPreferredScrollableViewportSize() {
138 return new Dimension(1, A_LOT);
139 }
140
141 public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
142 return 1;
143 }
144
145 public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
146 return 1;
147 }
148
149 public boolean getScrollableTracksViewportWidth() {
150 return true;
151 }
152
153 public boolean getScrollableTracksViewportHeight() {
154 return false;
155 }
156 }
157
158 private static class CommentsPanel extends JPanel {
159
160 private ScrollablePanel comments = new ScrollablePanel();
161 private JScrollPane scroll = new JScrollPane();
162 private List<CommentPanel> commentList = new ArrayList<CommentPanel>();
163
164 private Border border = BorderFactory.createTitledBorder("Comments");
165
166 public CommentsPanel(JIRAIssue issue) {
167 setBorder(border);
168 setLayout(new GridBagLayout());
169 GridBagConstraints gbc = new GridBagConstraints();
170 gbc.gridx = 0;
171 gbc.gridy = 0;
172 gbc.fill = GridBagConstraints.NONE;
173
174 gbc.gridy = 1;
175 gbc.gridwidth = 2;
176 gbc.fill = GridBagConstraints.HORIZONTAL;
177
178 ActionManager manager = ActionManager.getInstance();
179 ActionGroup group = (ActionGroup) manager.getAction("ThePlugin.JIRA.CommentsToolBar");
180 ActionToolbar toolbar = manager.createActionToolbar(issue.getKey(), group, true);
181
182 JComponent comp = toolbar.getComponent();
183 add(comp, gbc);
184
185 gbc.gridx = 0;
186 gbc.gridy = 2;
187 gbc.gridwidth = 2;
188 gbc.insets = new Insets(0, 0, 0, 0);
189 gbc.fill = GridBagConstraints.BOTH;
190 gbc.weightx = 1.0;
191 gbc.weighty = 1.0;
192 comments.setLayout(new VerticalFlowLayout());
193 scroll.setViewportView(comments);
194 scroll.getViewport().setOpaque(false);
195 scroll.setOpaque(false);
196 scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
197 scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
198 scroll.setBorder(BorderFactory.createEmptyBorder());
199 add(scroll, gbc);
200 }
201
202 public void setTitle(String title) {
203 border = BorderFactory.createTitledBorder(title);
204 setBorder(border);
205 }
206
207 public void addComment(final Project project, JIRAIssue issue, JIRAComment c, JIRAServer server) {
208 CommentPanel p = new CommentPanel(project, issue, c, server);
209 comments.add(p);
210 commentList.add(p);
211 }
212
213 public void clearComments() {
214 commentList.clear();
215 comments.removeAll();
216 }
217
218 public void setAllVisible(boolean visible) {
219 for (CommentPanel c : commentList) {
220 c.getShowHideButton().setState(visible);
221 }
222 }
223
224 public void scrollToFirst() {
225 SwingUtilities.invokeLater(new Runnable() {
226 public void run() {
227 scroll.getVerticalScrollBar().setValue(0);
228 }
229 });
230 }
231 }
232
233 private abstract static class AbstractShowHideButton extends JLabel {
234
235 private Icon right = IconLoader.findIcon("/icons/navigate_right_10.gif");
236 private Icon down = IconLoader.findIcon("/icons/navigate_down_10.gif");
237 private boolean shown = true;
238
239 public AbstractShowHideButton() {
240 setHorizontalAlignment(0);
241 setIcon(down);
242 setToolTipText(getTooltip());
243 addMouseListener(new MouseAdapter() {
244 @Override
245 public void mouseClicked(MouseEvent e) {
246 click();
247 }
248 });
249 }
250
251 public void setState(boolean visible) {
252 shown = visible;
253 setIcon(shown ? down : right);
254 setComponentVisible(shown);
255 }
256 public void click() {
257 shown = !shown;
258 setState(shown);
259 }
260
261 protected abstract void setComponentVisible(boolean visible);
262 protected abstract String getTooltip();
263 }
264
265 private static class ShowHideButton extends AbstractShowHideButton {
266 private JComponent body;
267 private JComponent container;
268
269 public ShowHideButton(JComponent body, JComponent container) {
270 this.body = body;
271 this.container = container;
272 }
273
274 @Override
275 protected void setComponentVisible(boolean visible) {
276 body.setVisible(visible);
277 container.validate();
278 container.getParent().validate();
279 }
280
281 @Override
282 protected String getTooltip() {
283 return "Collapse/Expand";
284 }
285 }
286
287 private static class UserLabel extends HyperlinkLabel {
288 UserLabel(final String serverUrl, final String userNameId) {
289 super(userNameId, UIUtil.getTableSelectionForeground(),
290 UIUtil.getTableSelectionBackground(), UIUtil.getTableSelectionForeground());
291 addListener(serverUrl, userNameId);
292 }
293
294 UserLabel(final String serverUrl, final String userName, final String userNameId) {
295 super(userName);
296 addListener(serverUrl, userNameId);
297 }
298
299 private void addListener(final String serverUrl, final String userNameId) {
300 addHyperlinkListener(new HyperlinkListener() {
301 public void hyperlinkUpdate(HyperlinkEvent e) {
302 BrowserUtil.launchBrowser(
303 serverUrl
304 + "/secure/ViewProfile.jspa?name="
305 + userNameId);
306 }
307 });
308 }
309 }
310
311 private static class BoldLabel extends JLabel {
312 public BoldLabel(String text) {
313 super(text);
314 setFont(getFont().deriveFont(Font.BOLD));
315 }
316
317 public BoldLabel() {
318 this("");
319 }
320 }
321
322 private static class WhiteLabel extends BoldLabel {
323 public WhiteLabel() {
324 setForeground(UIUtil.getTableSelectionForeground());
325 }
326 }
327
328 private static class CommentPanel extends JPanel {
329
330 private ShowHideButton btnShowHide;
331 private static final int GRID_WIDTH = 6;
332 private final Project project;
333
334 public CommentPanel(final Project project, final JIRAIssue issue, final JIRAComment comment, final JIRAServer server) {
335 this.project = project;
336 setOpaque(true);
337 setBackground(UIUtil.getTableSelectionBackground());
338
339 setLayout(new GridBagLayout());
340 GridBagConstraints gbc;
341 int gridx = 1;
342
343 JEditorPane commentBody = new JEditorPane();
344 btnShowHide = new ShowHideButton(commentBody, this);
345 gbc = new GridBagConstraints();
346 gbc.gridx = gridx++;
347 gbc.gridy = 0;
348 gbc.anchor = GridBagConstraints.WEST;
349 add(btnShowHide, gbc);
350
351 gbc = new GridBagConstraints();
352 gbc.gridx = gridx++;
353 gbc.gridy = 0;
354 gbc.anchor = GridBagConstraints.WEST;
355 gbc.insets = new Insets(0, Constants.DIALOG_MARGIN, 0, 0);
356 UserLabel ul = new UserLabel(server.getServer().getUrl(), comment.getAuthor());
357 add(ul, gbc);
358
359 final JLabel hyphen = new WhiteLabel();
360 hyphen.setText("-");
361 gbc = new GridBagConstraints();
362 gbc.gridx = gridx++;
363 gbc.gridy = 0;
364 gbc.anchor = GridBagConstraints.WEST;
365 gbc.insets = new Insets(0, Constants.DIALOG_MARGIN, 0, Constants.DIALOG_MARGIN);
366 add(hyphen, gbc);
367
368 final JLabel creationDate = new WhiteLabel();
369 creationDate.setText(comment.getCreationDate().getTime().toString());
370 gbc = new GridBagConstraints();
371 gbc.gridx = gridx++;
372 gbc.gridy = 0;
373 gbc.anchor = GridBagConstraints.WEST;
374 gbc.weightx = 1.0;
375 gbc.fill = GridBagConstraints.BOTH;
376 add(creationDate, gbc);
377
378 if (StackTraceDetector.containsStackTrace(comment.getBody())) {
379 HyperlinkLabel analyze = new HyperlinkLabel("Analyse stack trace", UIUtil.getTableSelectionForeground(),
380 UIUtil.getTableSelectionBackground(), UIUtil.getTableSelectionForeground());
381 analyze.addHyperlinkListener(new HyperlinkListener() {
382 public void hyperlinkUpdate(HyperlinkEvent e) {
383 StackTraceConsole stackTraceConsole = IdeaHelper.getProjectComponent(project, StackTraceConsole.class);
384 stackTraceConsole.print(issue,
385 "comment: " + comment.getAuthor() + " - " + creationDate.getText(), comment.getBody());
386 }
387 });
388 gbc.gridx++;
389 gbc.gridy = 0;
390 gbc.weightx = 0.0;
391 gbc.anchor = GridBagConstraints.EAST;
392 gbc.insets = new Insets(0, Constants.DIALOG_MARGIN, 0, 0);
393 add(analyze, gbc);
394 }
395
396 commentBody.setEditable(false);
397 commentBody.setOpaque(true);
398 commentBody.setBackground(UIUtil.getPanelBackground());
399 commentBody.setMargin(new Insets(0, Constants.DIALOG_MARGIN, 0, 0));
400 commentBody.setContentType("text/html");
401 commentBody.setText("<html><head></head><body>" + comment.getBody() + "</body></html>");
402 gbc = new GridBagConstraints();
403 gbc.gridx = 0;
404 gbc.gridy = 1;
405 gbc.gridwidth = GRID_WIDTH;
406 gbc.weightx = 1.0;
407 gbc.weighty = 1.0;
408 gbc.insets = new Insets(0, 0, 0, 0);
409 gbc.fill = GridBagConstraints.BOTH;
410 add(commentBody, gbc);
411 }
412
413 public AbstractShowHideButton getShowHideButton() {
414 return btnShowHide;
415 }
416 }
417
418 private static class DescriptionPanel extends JPanel {
419 public DescriptionPanel(final JIRAIssue issue) {
420 setLayout(new GridBagLayout());
421 GridBagConstraints gbc = new GridBagConstraints();
422
423 gbc.gridx = 0;
424 gbc.gridy = 0;
425
426 gbc.insets = new Insets(0, 0, 0, 0);
427 gbc.fill = GridBagConstraints.BOTH;
428 gbc.weightx = 1.0;
429 gbc.weighty = 1.0;
430
431 JEditorPane body = new JEditorPane();
432 JScrollPane sp = new JScrollPane(body,
433 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
434 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
435 sp.setBorder(BorderFactory.createEmptyBorder());
436 sp.setOpaque(false);
437 body.setEditable(false);
438 body.addHyperlinkListener(new HyperlinkListener() {
439 public void hyperlinkUpdate(HyperlinkEvent e) {
440 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
441 BrowserUtil.launchBrowser(e.getURL().toString());
442 }
443 }
444 });
445
446 body.setOpaque(false);
447 body.setBorder(BorderFactory.createEmptyBorder());
448 body.setContentType("text/html");
449 body.setText("<html><head></head><body>" + issue.getDescription() + "</body></html>");
450 sp.getViewport().setOpaque(false);
451 body.setCaretPosition(0);
452 add(sp, gbc);
453
454 Border b = BorderFactory.createTitledBorder("Description");
455 setBorder(b);
456 Insets i = b.getBorderInsets(this);
457 int minHeight = i.top + i.bottom;
458 setMinimumSize(new Dimension(0, minHeight));
459 }
460 }
461
462 private static class DetailsPanel extends JPanel {
463
464 private JLabel affectsVersions = new JLabel("Fetching...");
465 private JLabel fixVersions = new JLabel("Fetching...");
466 private JLabel components = new JLabel("Fetching...");
467
468 public DetailsPanel(final JIRAIssue issue) {
469 JPanel body = new JPanel();
470
471 setLayout(new GridBagLayout());
472 body.setLayout(new GridBagLayout());
473
474 GridBagConstraints gbc1 = new GridBagConstraints();
475 GridBagConstraints gbc2 = new GridBagConstraints();
476 gbc1.anchor = GridBagConstraints.FIRST_LINE_START;
477 gbc1.insets = new Insets(0, Constants.DIALOG_MARGIN, 0, Constants.DIALOG_MARGIN);
478 gbc2.anchor = GridBagConstraints.FIRST_LINE_START;
479 gbc2.fill = GridBagConstraints.HORIZONTAL;
480 gbc2.weightx = 1.0;
481 gbc1.gridx = 0;
482 gbc2.gridx = 1;
483 gbc1.gridy = 0;
484 gbc2.gridy = 0;
485
486 body.add(new BoldLabel("Type"), gbc1);
487 body.add(new JLabel(issue.getType(), CachedIconLoader.getIcon(issue.getTypeIconUrl()),
488 SwingConstants.LEFT), gbc2);
489 gbc1.gridy++;
490 gbc2.gridy++;
491 body.add(new BoldLabel("Status"), gbc1);
492 body.add(new JLabel(issue.getStatus(), CachedIconLoader.getIcon(issue.getStatusTypeUrl()),
493 SwingConstants.LEFT), gbc2);
494 gbc1.gridy++;
495 gbc2.gridy++;
496 body.add(new BoldLabel("Priority"), gbc1);
497 body.add(new JLabel(issue.getPriority(), CachedIconLoader.getIcon(issue.getPriorityIconUrl()),
498 SwingConstants.LEFT), gbc2);
499 gbc1.gridy++;
500 gbc2.gridy++;
501 body.add(new BoldLabel("Assignee"), gbc1);
502 body.add(new UserLabel(issue.getServerUrl(), issue.getAssignee(), issue.getAssigneeId()), gbc2);
503 gbc1.gridy++;
504 gbc2.gridy++;
505 body.add(new BoldLabel("Reporter"), gbc1);
506 body.add(new UserLabel(issue.getServerUrl(), issue.getReporter(), issue.getReporterId()), gbc2);
507 gbc1.gridy++;
508 gbc2.gridy++;
509 body.add(new BoldLabel("Resolution"), gbc1);
510 body.add(new JLabel(issue.getResolution()), gbc2);
511 gbc1.gridy++;
512 gbc2.gridy++;
513 body.add(new BoldLabel("Created"), gbc1);
514 body.add(new JLabel(issue.getCreated()), gbc2);
515 gbc1.gridy++;
516 gbc2.gridy++;
517 body.add(new BoldLabel("Updated"), gbc1);
518 body.add(new JLabel(issue.getUpdated()), gbc2);
519 gbc1.gridy++;
520 gbc2.gridy++;
521 body.add(new BoldLabel("Affects Version/s"), gbc1);
522 body.add(affectsVersions, gbc2);
523 gbc1.gridy++;
524 gbc2.gridy++;
525 body.add(new BoldLabel("Fix Version/s"), gbc1);
526 body.add(fixVersions, gbc2);
527 gbc1.gridy++;
528 gbc2.gridy++;
529 body.add(new BoldLabel("Component/s"), gbc1);
530 body.add(components, gbc2);
531
532 gbc1.gridy++;
533 gbc1.weighty = 1.0;
534 gbc1.fill = GridBagConstraints.VERTICAL;
535 body.add(new JPanel(), gbc1);
536
537 GridBagConstraints gbc = new GridBagConstraints();
538 gbc.gridx = 0;
539 gbc.gridy = 0;
540 gbc.weightx = 1.0;
541 gbc.weighty = 1.0;
542 gbc.fill = GridBagConstraints.BOTH;
543 JScrollPane scroll = new JScrollPane(body);
544 scroll.setBorder(BorderFactory.createEmptyBorder());
545 add(scroll, gbc);
546
547 Border b = BorderFactory.createTitledBorder("Details");
548 setBorder(b);
549 Insets i = b.getBorderInsets(this);
550 setMinimumSize(new Dimension(0, i.top + i.bottom));
551 }
552
553 public JLabel getAffectVersionsLabel() {
554 return affectsVersions;
555 }
556
557 public JLabel getFixVersionsLabel() {
558 return fixVersions;
559 }
560
561 public JLabel getComponentsLabel() {
562 return components;
563 }
564 }
565
566 private static class SummaryPanel extends JPanel {
567
568 private DetailsPanel details;
569 private static final int THICKNESS = 6;
570
571 public SummaryPanel(final JIRAIssue issue) {
572 setLayout(new GridBagLayout());
573 GridBagConstraints gbc = new GridBagConstraints();
574
575 gbc.gridx = 0;
576 gbc.gridy = 0;
577 gbc.fill = GridBagConstraints.HORIZONTAL;
578
579 ActionManager manager = ActionManager.getInstance();
580 ActionGroup group = (ActionGroup) manager.getAction("ThePlugin.JIRA.EditorToolBar");
581 ActionToolbar toolbar = manager.createActionToolbar(issue.getKey(), group, true);
582
583 JComponent comp = toolbar.getComponent();
584 add(comp, gbc);
585
586 gbc.gridy = 1;
587 gbc.gridx = 0;
588 gbc.anchor = GridBagConstraints.LINE_START;
589 gbc.fill = GridBagConstraints.BOTH;
590 gbc.weightx = 1.0;
591 JEditorPane summary = new JEditorPane();
592 summary.setContentType("text/html");
593 Color bg = UIUtil.getTableSelectionBackground();
594 Color fg = UIUtil.getTableSelectionForeground();
595 String bgColor = ColorToHtml.getHtmlFromColor(bg);
596 String fgColor = ColorToHtml.getHtmlFromColor(fg);
597 String txt = "<html><body bgcolor=" + bgColor + " color=" + fgColor
598 + "><font size=\"+1\"><a href=\"" + issue.getIssueUrl() + "\">"
599 + issue.getKey() + "</a> " + issue.getSummary() + "</font></body></html>";
600 summary.setText(txt);
601 summary.setEditable(false);
602 summary.setFont(summary.getFont().deriveFont(Font.BOLD));
603 summary.setBackground(bg);
604 summary.setOpaque(true);
605 JPanel p = new JPanel();
606 p.setLayout(new GridBagLayout());
607 p.setBorder(BorderFactory.createLineBorder(bg, THICKNESS));
608 GridBagConstraints gbcp = new GridBagConstraints();
609 gbcp.fill = GridBagConstraints.BOTH;
610 gbcp.weightx = 1.0;
611 gbcp.weighty = 1.0;
612 gbcp.gridx = 0;
613 gbcp.gridy = 0;
614 p.add(summary, gbcp);
615 add(p, gbc);
616
617 gbc.gridx = 0;
618 gbc.gridy = 2;
619 gbc.gridwidth = 2;
620 gbc.fill = GridBagConstraints.BOTH;
621 gbc.weightx = 1.0;
622 gbc.weighty = 1.0;
623 Splitter split = new Splitter(true);
624 split.setFirstComponent(new DescriptionPanel(issue));
625 details = new DetailsPanel(issue);
626 split.setSecondComponent(details);
627 split.setShowDividerControls(true);
628 split.setHonorComponentsMinimumSize(true);
629 add(split, gbc);
630 if (issue.getDescription().length() == 0) {
631 split.setProportion(0);
632 }
633 }
634
635 public void setAffectsVersions(String[] versions) {
636 setLabelText(details.getAffectVersionsLabel(), versions);
637 }
638
639 public void setFixVersions(String[] versions) {
640 setLabelText(details.getFixVersionsLabel(), versions);
641 }
642
643 public void setComponents(String[] components) {
644 setLabelText(details.getComponentsLabel(), components);
645 }
646
647 private void setLabelText(JLabel label, String[] texts) {
648 if (texts.length == 0) {
649 label.setText("None");
650 } else {
651
652 StringBuffer txt = new StringBuffer();
653 for (int i = 0; i < texts.length; ++i) {
654 if (i > 0) {
655 txt.append(", ");
656 }
657 txt.append(texts[i]);
658 }
659 label.setText(txt.toString());
660 }
661 }
662 }
663
664 public static class JIRAFileEditor implements FileEditor {
665
666 private final JIRAServerFacade facade;
667 private final JIRAServer server;
668
669 private JPanel mainPanel;
670 private final Project project;
671 private JIRAIssue issue;
672 private CommentsPanel commentsPanel;
673 private SummaryPanel summaryPanel;
674 private boolean hasStackTrace;
675
676 private JIRAFileEditor() {
677 mainPanel = new JPanel();
678 mainPanel.setBackground(Color.RED);
679
680 mainPanel.add(new JLabel("Can't view issue, something is wrong"));
681 facade = null;
682 server = null;
683 project = null;
684 }
685
686 public JIRAFileEditor(Project project, JIRAIssue issue) {
687 this.project = project;
688 this.issue = issue;
689 facade = JIRAServerFacadeImpl.getInstance();
690 server = IdeaHelper.getCurrentJIRAServer(project);
691 editorMap.put(issue.getKey(), this);
692
693 hasStackTrace = StackTraceDetector.containsStackTrace(Html2text.translate(issue.getDescription()));
694
695 setupUI();
696 }
697
698 private void setupUI() {
699 mainPanel = new JPanel();
700 mainPanel.setLayout(new GridBagLayout());
701 final GridBagConstraints gbc = new GridBagConstraints();
702
703 gbc.gridx = 0;
704 gbc.gridy = 0;
705
706 gbc.insets = new Insets(0, 0, 0, 0);
707 gbc.fill = GridBagConstraints.BOTH;
708 gbc.weightx = 1.0;
709 gbc.weighty = 1.0;
710 Splitter split = new Splitter(true);
711 split.setShowDividerControls(true);
712 split.setHonorComponentsMinimumSize(true);
713 summaryPanel = new SummaryPanel(issue);
714 split.setFirstComponent(summaryPanel);
715 commentsPanel = new CommentsPanel(issue);
716 split.setSecondComponent(commentsPanel);
717 mainPanel.add(split, gbc);
718 getMoreIssueDetails();
719 refreshComments();
720 }
721
722 private String[] getStringArray(List<JIRAConstant> l) {
723 List<String> sl = new ArrayList<String>(l.size());
724 for (JIRAConstant c : l) {
725 sl.add(c.getName());
726 }
727 return sl.toArray(new String[l.size()]);
728 }
729
730 public void addComment() {
731 final IssueComment issueComment = new IssueComment(issue.getKey());
732 issueComment.show();
733 if (issueComment.isOK()) {
734 Runnable runnable = new Runnable() {
735 public void run() {
736 try {
737 if (server != null) {
738 facade.addComment(server.getServer(), issue, issueComment.getComment());
739 refreshComments();
740 }
741 } catch (JIRAException e) {
742 final String msg = e.getMessage();
743 SwingUtilities.invokeLater(new Runnable() {
744 public void run() {
745 Messages.showMessageDialog(
746 "Failed to add comment to issue " + issue.getKey() + ": " + msg,
747 "Error", Messages.getErrorIcon());
748 }
749 });
750 }
751 }
752 };
753 new Thread(runnable, "atlassian-idea-plugin comment issue from editor").start();
754 }
755 }
756
757 public synchronized void getMoreIssueDetails() {
758 if ((issue.getAffectsVersions() == null)
759 || (issue.getFixVersions() == null)
760 || (issue.getComponents() == null)) {
761
762 Runnable runnable = new Runnable() {
763 private String[] errorString = null;
764
765 public void run() {
766
767 try {
768 if (server != null) {
769 final JIRAIssue issueDetails = facade.getIssueDetails(server.getServer(), issue);
770 issue.setAffectsVersions(issueDetails.getAffectsVersions());
771 issue.setFixVersions(issueDetails.getFixVersions());
772 issue.setComponents(issueDetails.getComponents());
773 }
774 } catch (JIRAException e) {
775 errorString = new String[] { "Cannot retrieve: " + e.getMessage() };
776 }
777 SwingUtilities.invokeLater(new Runnable() {
778 public void run() {
779 if (errorString == null) {
780 summaryPanel.setAffectsVersions(getStringArray(issue.getAffectsVersions()));
781 summaryPanel.setFixVersions(getStringArray(issue.getFixVersions()));
782 summaryPanel.setComponents(getStringArray(issue.getComponents()));
783 } else {
784 summaryPanel.setAffectsVersions(errorString);
785 summaryPanel.setFixVersions(errorString);
786 summaryPanel.setComponents(errorString);
787 }
788 }
789 });
790 }
791 };
792 new Thread(runnable, "atlassian-idea-plugin get issue details").start();
793 } else {
794 summaryPanel.setAffectsVersions(getStringArray(issue.getAffectsVersions()));
795 summaryPanel.setFixVersions(getStringArray(issue.getFixVersions()));
796 summaryPanel.setComponents(getStringArray(issue.getComponents()));
797 }
798 }
799
800 public void refreshComments() {
801 commentsPanel.clearComments();
802 commentsPanel.setTitle("Fetching comments...");
803 final Runnable runnable = new Runnable() {
804 public void run() {
805 try {
806 if (server != null) {
807 final List<JIRAComment> comments = facade.getComments(server.getServer(), issue);
808 SwingUtilities.invokeLater(new Runnable() {
809 public void run() {
810 int size = comments.size();
811 if (size > 0) {
812 commentsPanel.setTitle("Comments (" + comments.size() + ")");
813 for (JIRAComment c : comments) {
814 commentsPanel.addComment(project, issue, c, server);
815 }
816 commentsPanel.validate();
817 commentsPanel.scrollToFirst();
818 } else {
819 commentsPanel.setTitle("No comments");
820 }
821 }
822 });
823 }
824 } catch (JIRAException e) {
825 commentsPanel.setTitle("Cannot fetch comments: " + e.getMessage());
826 }
827 }
828 };
829 new Thread(runnable, "atlassian-idea-plugin refresh comments").start();
830 }
831
832 public JIRAIssue getIssue() {
833 return issue;
834 }
835
836 public boolean hasStackTraceInDescription() {
837 return hasStackTrace;
838 }
839
840 public void analyzeDescriptionStackTrace() {
841 StackTraceConsole stackTraceConsole = IdeaHelper.getProjectComponent(project, StackTraceConsole.class);
842 stackTraceConsole.print(issue, "description", Html2text.translate(issue.getDescription()));
843 }
844
845 public void setCommentsExpanded(boolean expanded) {
846 commentsPanel.setAllVisible(expanded);
847 }
848
849 @NotNull
850 public JComponent getComponent() {
851 return mainPanel;
852 }
853
854 @Nullable
855 public JComponent getPreferredFocusedComponent() {
856 return mainPanel;
857 }
858
859 @NonNls
860 @NotNull
861 public String getName() {
862 return "JIRA Issue View";
863 }
864
865 @NotNull
866 public FileEditorState getState(@NotNull FileEditorStateLevel fileEditorStateLevel) {
867 return DummyFileEditorState.DUMMY;
868 }
869
870 public void setState(@NotNull FileEditorState fileEditorState) {
871 }
872
873 public boolean isModified() {
874 return false;
875 }
876
877 public boolean isValid() {
878 return true;
879 }
880
881 public void selectNotify() {
882 }
883
884 public void deselectNotify() {
885 }
886
887 public void addPropertyChangeListener(@NotNull PropertyChangeListener propertyChangeListener) {
888 }
889
890 public void removePropertyChangeListener(@NotNull PropertyChangeListener propertyChangeListener) {
891 }
892
893 @Nullable
894 public BackgroundEditorHighlighter getBackgroundHighlighter() {
895 return null;
896 }
897
898 @Nullable
899 public FileEditorLocation getCurrentLocation() {
900 return null;
901 }
902
903 @Nullable
904 public StructureViewBuilder getStructureViewBuilder() {
905 return null;
906 }
907
908 public <T> T getUserData(Key<T> tKey) {
909 return null;
910 }
911
912 public <T> void putUserData(Key<T> tKey, T t) {
913 }
914
915 public void dispose() {
916 editorMap.remove(issue.getKey());
917 }
918 }
919
920 private static HashMap<String, JIRAFileEditor> editorMap = new HashMap<String, JIRAFileEditor>();
921
922 public static JIRAFileEditor getEditorByKey(String key) {
923 if (editorMap.containsKey(key)) {
924 return editorMap.get(key);
925 }
926 return null;
927 }
928 }