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