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  package com.atlassian.theplugin.idea.crucible;
17  
18  import com.atlassian.theplugin.commons.cfg.CfgManager;
19  import com.atlassian.theplugin.commons.cfg.ServerId;
20  import com.atlassian.theplugin.commons.configuration.PluginConfiguration;
21  import com.atlassian.theplugin.commons.crucible.CrucibleReviewListener;
22  import com.atlassian.theplugin.commons.crucible.ValueNotYetInitialized;
23  import com.atlassian.theplugin.commons.crucible.api.model.*;
24  import com.atlassian.theplugin.commons.crucible.api.model.notification.CrucibleNotification;
25  import com.atlassian.theplugin.commons.remoteapi.ServerData;
26  import com.atlassian.theplugin.commons.util.DateUtil;
27  import com.atlassian.theplugin.configuration.WorkspaceConfigurationBean;
28  import com.atlassian.theplugin.idea.Constants;
29  import com.atlassian.theplugin.idea.MultiTabToolWindow;
30  import com.atlassian.theplugin.idea.ProgressAnimationProvider;
31  import com.atlassian.theplugin.idea.ThePluginProjectComponent;
32  import com.atlassian.theplugin.idea.crucible.editor.ChangeViewer;
33  import com.atlassian.theplugin.idea.crucible.editor.CommentHighlighter;
34  import com.atlassian.theplugin.idea.crucible.tree.AtlassianTreeWithToolbar;
35  import com.atlassian.theplugin.idea.crucible.tree.ReviewItemTreePanel;
36  import com.atlassian.theplugin.idea.ui.BoldLabel;
37  import com.atlassian.theplugin.idea.ui.SwingAppRunner;
38  import com.atlassian.theplugin.util.Htmlizer;
39  import com.intellij.ide.BrowserUtil;
40  import com.intellij.openapi.actionSystem.*;
41  import com.intellij.openapi.progress.ProgressIndicator;
42  import com.intellij.openapi.progress.ProgressManager;
43  import com.intellij.openapi.progress.Task;
44  import com.intellij.openapi.project.Project;
45  import com.intellij.openapi.ui.VerticalFlowLayout;
46  import com.intellij.openapi.util.IconLoader;
47  import com.intellij.util.ui.UIUtil;
48  import org.jetbrains.annotations.NonNls;
49  import org.jetbrains.annotations.NotNull;
50  import org.jetbrains.annotations.Nullable;
51  
52  import javax.swing.*;
53  import javax.swing.event.HyperlinkEvent;
54  import javax.swing.event.HyperlinkListener;
55  import java.awt.*;
56  import java.awt.event.ComponentAdapter;
57  import java.awt.event.ComponentEvent;
58  import java.util.Date;
59  
60  /**
61   * User: pmaruszak
62   */
63  public class ReviewDetailsToolWindow extends MultiTabToolWindow implements DataProvider {
64  	private static final String TOOL_WINDOW_TITLE = "Reviews - Crucible";
65  	private ReviewAdapter reviewAdapter;
66  	private final CfgManager cfgManager;
67  	private final Project project;
68  	private final ThePluginProjectComponent pluginProjectComponent;
69  	private PluginConfiguration pluginConfiguration;
70  	private WorkspaceConfigurationBean workspaceConfiguration;
71  
72  	private ReviewPanel contentPanel;
73  	private ReviewContentParameters contentParams;
74  
75  
76  	protected ReviewDetailsToolWindow(@NotNull CfgManager cfgManager, @NotNull final Project project,
77  			@NotNull final ThePluginProjectComponent pluginProjectComponent,
78  			@NotNull final PluginConfiguration pluginConfiguration,
79  			@NotNull final WorkspaceConfigurationBean workspaceConfiguration) {
80  		super(true);
81  		this.cfgManager = cfgManager;
82  		this.project = project;
83  		this.pluginProjectComponent = pluginProjectComponent;
84  		this.pluginConfiguration = pluginConfiguration;
85  		this.workspaceConfiguration = workspaceConfiguration;
86  	}
87  
88  	@Override
89  	protected String getContentKey(ContentParameters params) {
90  		ReviewContentParameters cParams = (ReviewContentParameters) params;
91  		ReviewAdapter ra = cParams != null ? cParams.reviewAdapter : null;
92  		String key = "";
93  
94  		if (ra != null) {
95  			key = ra.getServerData().getServerId() + ra.getPermId().getId();
96  		}
97  		return key;
98  	}
99  
100 	@Override
101 	protected ContentPanel createContentPanel(ContentParameters params) {
102 		pluginConfiguration.getGeneralConfigurationData().bumpCounter("r");
103 		contentPanel = new ReviewPanel((ReviewContentParameters) params);
104 		return contentPanel;
105 	}
106 
107 	public void closeToolWindow(AnActionEvent event) {
108 		super.closeToolWindow(TOOL_WINDOW_TITLE, event);
109 		CommentHighlighter.removeCommentsInEditors(project);
110 		ChangeViewer.removeHighlightersInEditors(project);
111 		this.contentParams.reviewAdapter.clearContentCache();
112 	}
113 
114 	public Object getData(@NonNls final String dataId) {
115 		if (dataId.equals(Constants.REVIEW)) {
116 			return reviewAdapter;
117 		}
118 		return null;
119 	}
120 
121 	/**
122 	 * Select 'file and comments' tab and required comment but only if panel contains provided review
123 	 *
124 	 * @param review
125 	 * @param file
126 	 * @param comment
127 	 */
128 	public void selectVersionedComment(final ReviewAdapter review, final CrucibleFileInfo file, final Comment comment) {
129 		if (contentParams.reviewAdapter.equals(review)) {
130 			contentPanel.selectVersionedComment(file, comment);
131 		}
132 	}
133 
134 	public void selectGeneralComment(final ReviewAdapter review, final Comment comment) {
135 		if (contentParams.reviewAdapter.equals(review)) {
136 			contentPanel.selectGeneralComment(comment);
137 		}
138 	}
139 
140 	public void selectFile(final ReviewAdapter review, final CrucibleFileInfo file) {
141 		if (contentParams.reviewAdapter.equals(review)) {
142 			contentPanel.selectFile(file);
143 		}
144 	}
145 
146 	private final class ReviewContentParameters implements MultiTabToolWindow.ContentParameters {
147 		private final ReviewAdapter reviewAdapter;
148 		private boolean refreshDetails;
149 
150 		private ReviewContentParameters(ReviewAdapter reviewAdapter, final boolean refreshDetails) {
151 			this.reviewAdapter = reviewAdapter;
152 			this.refreshDetails = refreshDetails;
153 		}
154 	}
155 
156 	public void showReview(ReviewAdapter adapter, boolean refreshDetails) {
157 		this.reviewAdapter = adapter;
158 		if (workspaceConfiguration.getCrucibleConfiguration() != null) {
159 			workspaceConfiguration.getCrucibleConfiguration()
160 					.getCrucibleFilters().getRecenltyOpenFilter().addRecentlyOpenReview(adapter);
161 		}
162 		contentParams = new ReviewContentParameters(adapter, refreshDetails);
163 		showToolWindow(project, contentParams, TOOL_WINDOW_TITLE,
164 				Constants.CRUCIBLE_REVIEW_PANEL_ICON, Constants.CRUCIBLE_REVIEW_TAB_ICON);
165 	}
166 
167 	public AtlassianTreeWithToolbar getAtlassianTreeWithToolbar() {
168 		if (contentParams != null && getContentPanel(getContentKey(contentParams)) != null) {
169 			return ((ReviewPanel) getContentPanel(getContentKey(contentParams)))
170 					.commentsPanel.getReviewItemTreePanel().getAtlassianTreeWithToolbar();
171 		}
172 		return null;
173 	}
174 
175 	public void switchFilter() {
176 		if (contentParams != null && getContentPanel(getContentKey(contentParams)) != null) {
177 			((ReviewPanel) getContentPanel(getContentKey(contentParams))).commentsPanel.getReviewItemTreePanel().switchFilter();
178 		}
179 	}
180 
181 	private final class ReviewPanel extends MultiTabToolWindow.ContentPanel implements CrucibleReviewListener {
182 		private final ReviewContentParameters params;
183 		private DetailsPanel detailsPanel;
184 		private SummaryPanel summaryPanel;
185 		private CommentsPanel commentsPanel;
186 		private static final String TAB_DETAILS = "Details";
187 		private static final String TAB_FILES_AND_COMMENTS = "Files and Comments";
188 		private JTabbedPane tabs;
189 
190 		private ReviewPanel(ReviewContentParameters params) {
191 			this.params = params;
192 
193 			tabs = new JTabbedPane();
194 			detailsPanel = new DetailsPanel(params.reviewAdapter);
195 
196 			tabs.addTab(TAB_DETAILS, detailsPanel);
197 			commentsPanel = new CommentsPanel(params.refreshDetails);
198 			tabs.addTab(TAB_FILES_AND_COMMENTS, commentsPanel);
199 
200 			setLayout(new GridBagLayout());
201 			GridBagConstraints gbc = new GridBagConstraints();
202 			gbc.fill = GridBagConstraints.BOTH;
203 			gbc.gridx = 0;
204 			gbc.gridy = 0;
205 			gbc.weightx = 1.0;
206 			gbc.weighty = 0.0;
207 			gbc.insets = new Insets(Constants.DIALOG_MARGIN / 2, Constants.DIALOG_MARGIN, 0, 0);
208 			summaryPanel = new SummaryPanel();
209 			add(summaryPanel, gbc);
210 			gbc.gridy++;
211 			gbc.weighty = 1.0;
212 			gbc.insets = new Insets(0, 0, 0, 0);
213 			add(tabs, gbc);
214 
215 			if (params.reviewAdapter != null) {
216 				params.reviewAdapter.addReviewListener(this);
217 			}
218 			refresh();
219 		}
220 
221 		public void refresh() {
222 
223 		}
224 
225 		@Override
226 		public String getTitle() {
227 			return params.reviewAdapter.getPermId().getId();
228 		}
229 
230 		@Override
231 		public void unregister() {
232 			if (params != null) {
233 				params.reviewAdapter.removeReviewListener(this);
234 			}
235 
236 			commentsPanel.unregisterListeners();
237 		}
238 
239 		public String getKey() {
240 			if (params != null && params.reviewAdapter != null) {
241 				ReviewAdapter ra = params.reviewAdapter;
242 				return ra.getPermId().getId();
243 			}
244 			return "";
245 		}
246 
247 		public ContentParameters getContentParameters() {
248 			return params;
249 		}
250 
251 		public CommentsPanel getCommentsPanel() {
252 			return commentsPanel;
253 		}
254 
255 		public void createdOrEditedVersionedCommentReply(final ReviewAdapter review, final PermId file,
256 				final VersionedComment parentComment,
257 				final
258 				VersionedComment comment) {
259 		}
260 
261 		public void createdOrEditedGeneralCommentReply(final ReviewAdapter review, final GeneralComment parentComment,
262 				final GeneralComment comment) {
263 		}
264 
265 		public void createdOrEditedGeneralComment(final ReviewAdapter review, final GeneralComment comment) {
266 		}
267 
268 		public void createdOrEditedVersionedComment(final ReviewAdapter review, final PermId file,
269 				final VersionedComment comment) {
270 		}
271 
272 		public void removedComment(final ReviewAdapter review, final Comment comment) {
273 		}
274 
275 		public void publishedGeneralComment(final ReviewAdapter review, final GeneralComment comment) {
276 		}
277 
278 		public void publishedVersionedComment(final ReviewAdapter review, final PermId filePermId,
279 				final VersionedComment comment) {
280 		}
281 
282 		public void reviewChanged(final ReviewAdapter review, final java.util.List<CrucibleNotification> notifications) {
283 			summaryPanel.refresh();
284 			detailsPanel.refresh();
285 		}
286 
287 		public void selectVersionedComment(final CrucibleFileInfo file, final Comment comment) {
288 			// select tab
289 			tabs.setSelectedComponent(commentsPanel);
290 
291 			// select comment
292 			commentsPanel.selectVersionedComment(file, comment);
293 		}
294 
295 		public void selectGeneralComment(final Comment comment) {
296 			// select tab
297 			tabs.setSelectedComponent(commentsPanel);
298 
299 			// select comment
300 			commentsPanel.selectGeneralComment(comment);
301 		}
302 
303 		public void selectFile(final CrucibleFileInfo file) {
304 			// select tab
305 			tabs.setSelectedComponent(commentsPanel);
306 
307 			// select comment
308 			commentsPanel.selectFile(file);
309 		}
310 
311 		private final class CommentsPanel extends JPanel {
312 			private ReviewItemTreePanel reviewItemTreePanel;
313 			private ProgressAnimationProvider progressAnimation = new ProgressAnimationProvider();
314 
315 			private CommentsPanel(final boolean retrieveDetails) {
316 				super(new BorderLayout());
317 				setBackground(UIUtil.getTreeTextBackground());
318 				reviewItemTreePanel = new ReviewItemTreePanel(cfgManager, project,
319 						CrucibleFilteredModelProvider.Filter.FILES_ALL,
320 						pluginProjectComponent);
321 				reviewItemTreePanel.getProgressAnimation().configure(reviewItemTreePanel,
322 						reviewItemTreePanel, BorderLayout.CENTER);
323 				add(reviewItemTreePanel, BorderLayout.CENTER);
324 
325 				registerListeners();
326 				progressAnimation.configure(this, reviewItemTreePanel, BorderLayout.CENTER);
327 				progressAnimation.startProgressAnimation();
328 
329 				Task.Backgroundable task = new Task.Backgroundable(project, "Retrieving Crucible Data", false) {
330 					@Override
331 					public void run(@NotNull final ProgressIndicator indicator) {
332 						reviewItemTreePanel.showReview(params.reviewAdapter, retrieveDetails);
333 					}
334 
335 					@Override
336 					public void onCancel() {
337 						progressAnimation.stopProgressAnimation();
338 					}
339 
340 					@Override
341 					public void onSuccess() {
342 						progressAnimation.stopProgressAnimation();
343 					}
344 				};
345 
346 				ProgressManager.getInstance().run(task);
347 			}
348 
349 			private void registerListeners() {
350 				reviewItemTreePanel.startListeningForCredentialChanges(project, params.reviewAdapter);
351 				params.reviewAdapter.addReviewListener(reviewItemTreePanel.getReviewListener());
352 			}
353 
354 			public void unregisterListeners() {
355 				reviewItemTreePanel.stopListeningForCredentialChanges();
356 				params.reviewAdapter.removeReviewListener(reviewItemTreePanel.getReviewListener());
357 			}
358 
359 			public ReviewItemTreePanel getReviewItemTreePanel() {
360 				return reviewItemTreePanel;
361 			}
362 
363 			public void selectVersionedComment(final CrucibleFileInfo file, final Comment comment) {
364 				reviewItemTreePanel.selectVersionedComment(file, comment);
365 			}
366 
367 			public void selectGeneralComment(final Comment comment) {
368 				reviewItemTreePanel.selectGeneralComment(comment);
369 			}
370 
371 			public void selectFile(final CrucibleFileInfo file) {
372 				reviewItemTreePanel.selectFile(file);
373 			}
374 		}
375 
376 		private class SummaryPanel extends JPanel implements DataProvider {
377 
378 			private JEditorPane summary;
379 
380 			public SummaryPanel() {
381 				setLayout(new GridBagLayout());
382 				GridBagConstraints gbc = new GridBagConstraints();
383 
384 				gbc.gridy = 0;
385 				gbc.gridx = 0;
386 				gbc.anchor = GridBagConstraints.LINE_START;
387 				gbc.fill = GridBagConstraints.HORIZONTAL;
388 				gbc.weightx = 1.0;
389 				summary = new JEditorPane();
390 				summary.setContentType("text/html");
391 				summary.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true);
392 				refresh();
393 				summary.setEditable(false);
394 				summary.addHyperlinkListener(new HyperlinkListener() {
395 					public void hyperlinkUpdate(HyperlinkEvent e) {
396 						if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
397 							BrowserUtil.launchBrowser(e.getURL().toString());
398 						}
399 					}
400 				});
401 
402 				summary.setFont(summary.getFont().deriveFont(Font.BOLD));
403 				summary.setOpaque(false);
404 				JPanel p = new JPanel();
405 				p.setLayout(new GridBagLayout());
406 				GridBagConstraints gbcp = new GridBagConstraints();
407 				gbcp.fill = GridBagConstraints.BOTH;
408 				gbcp.weightx = 1.0;
409 				gbcp.weighty = 1.0;
410 				gbcp.gridx = 0;
411 				gbcp.gridy = 0;
412 				p.add(summary, gbcp);
413 				add(p, gbc);
414 
415 				gbc.gridy++;
416 
417 				ActionManager manager = ActionManager.getInstance();
418 				if (manager != null) {
419 					ActionGroup group = (ActionGroup) manager.getAction("ThePlugin.Reviews.SingleReview.ToolBar");
420 					ActionToolbar toolbar = manager.createActionToolbar(getContentKey(params), group, true);
421 					toolbar.setTargetComponent(this);
422 					JComponent comp = toolbar.getComponent();
423 					add(comp, gbc);
424 				}
425 			}
426 
427 			public void refresh() {
428 				String txt = "<html><body><a href=\"" + params.reviewAdapter.getReviewUrl() + "\">"
429 						+ params.reviewAdapter.getPermId().getId() + "</a> "
430 						+ params.reviewAdapter.getName() + "</body></html>";
431 				summary.setText(txt);
432 			}
433 
434 			@Nullable
435 			public Object getData(@NonNls String dataId) {
436 				if (dataId.equals(Constants.REVIEW_TOOL_WINDOW)) {
437 					return this;
438 				} else if (dataId.equals(Constants.REVIEW_WINDOW_ENABLED)) {
439 					return true;
440 				} else if (dataId.equals(Constants.REVIEW) && contentPanel != null && params != null) {
441 					return params.reviewAdapter;
442 				}
443 				return null;
444 			}
445 		}
446 
447 	}
448 
449 	public ReviewAdapter getReview() {
450 		return reviewAdapter;
451 	}
452 }
453 
454 // kalamon: I absolutely bloody hate hate hate package-scope non-inner classes
455 
456 // piggy-backed on the main class of the file. To the author of this class - you suck.
457 class DetailsPanel extends JPanel {
458 	private JScrollPane scroll;
459 
460 	private final ReviewAdapter ra;
461 	private static final int MAX_DISPLAYED_LINK_LENGTH = 80;
462 
463 	public DetailsPanel(final ReviewAdapter reviewAdapter) {
464 		this.ra = reviewAdapter;
465 		setLayout(new GridBagLayout());
466 
467 		GridBagConstraints gbc = new GridBagConstraints();
468 		gbc.gridx = 0;
469 		gbc.gridy = 0;
470 		gbc.weightx = 1.0;
471 		gbc.weighty = 1.0;
472 		gbc.fill = GridBagConstraints.BOTH;
473 
474 		scroll = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
475 		final JPanel panel = createBody();
476 		scroll.setViewportView(panel);
477 		scroll.setBorder(BorderFactory.createEmptyBorder());
478 		add(scroll, gbc);
479 	}
480 
481 	private JPanel createBody() {
482 		final JPanel body = new JPanel();
483 
484 		body.setLayout(new GridBagLayout());
485 		body.setOpaque(true);
486 		body.setBackground(Color.WHITE);
487 
488 		GridBagConstraints gbc1 = new GridBagConstraints();
489 		GridBagConstraints gbc2 = new GridBagConstraints();
490 		gbc1.anchor = GridBagConstraints.FIRST_LINE_START;
491 		gbc2.anchor = GridBagConstraints.FIRST_LINE_START;
492 		gbc1.insets = new Insets(Constants.DIALOG_MARGIN / 2, Constants.DIALOG_MARGIN,
493 				Constants.DIALOG_MARGIN / 2, Constants.DIALOG_MARGIN);
494 		gbc2.insets = new Insets(Constants.DIALOG_MARGIN / 2, Constants.DIALOG_MARGIN,
495 				Constants.DIALOG_MARGIN / 2, Constants.DIALOG_MARGIN);
496 		gbc2.fill = GridBagConstraints.HORIZONTAL;
497 		gbc2.weightx = 1.0;
498 		gbc1.gridx = 0;
499 		gbc2.gridx = gbc1.gridx + 1;
500 		gbc1.gridy = 0;
501 		gbc2.gridy = 0;
502 
503 		body.add(new BoldLabel("Statement of Objectives"), gbc1);
504 
505 		final JEditorPane statementOfObjectives = new JEditorPane() {
506 
507 		};
508 		statementOfObjectives.setEditable(false);
509 		statementOfObjectives.setOpaque(true);
510 		statementOfObjectives.setBackground(Color.WHITE);
511 		statementOfObjectives.setContentType("text/html");
512 		statementOfObjectives.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true);
513 		Htmlizer htmlizer = new Htmlizer(MAX_DISPLAYED_LINK_LENGTH);
514 		String sooText = htmlizer.htmlizeHyperlinks(ra.getDescription());
515 		sooText = htmlizer.replaceWhitespace(sooText);
516 		statementOfObjectives.setText("<html><body>" + sooText + "</body></html>");
517 		statementOfObjectives.addHyperlinkListener(new HyperlinkListener() {
518 			public void hyperlinkUpdate(HyperlinkEvent e) {
519 				if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
520 					BrowserUtil.launchBrowser(e.getURL().toString());
521 				}
522 			}
523 		});
524 
525 		statementOfObjectives.setBorder(null);
526 		body.add(statementOfObjectives, gbc2);
527 		scroll.addComponentListener(new ComponentAdapter() {
528 			@Override
529 			public void componentResized(ComponentEvent e) {
530 ////				statementOfObjectives.doLayout();
531 //				System.out.println(statementOfObjectives.getX());
532 //				int preferredWidth = e.getComponent().getWidth() - statementOfObjectives.getX();
533 //				System.out.println(preferredWidth);
534 //				statementOfObjectives.setSize(preferredWidth, 100);
535 //				final int prefHeight = statementOfObjectives.getPreferredSize().height;
536 //				System.out.println("H" + prefHeight);
537 //				statementOfObjectives.setPreferredSize(new Dimension(preferredWidth , prefHeight));
538 //				statementOfObjectives.setSize(preferredWidth, prefHeight);
539 //				body.validate();
540 ////				invalidate();
541 //
542 //
543 				body.setPreferredSize(null);
544 
545 				body.setPreferredSize(new Dimension(scroll.getViewport().getWidth(),
546 						body.getPreferredSize().height));
547 
548 				body.validate();
549 			}
550 		});
551 
552 		gbc1.gridy++;
553 		gbc2.gridy++;
554 		gbc1.insets = new Insets(0, Constants.DIALOG_MARGIN,
555 				Constants.DIALOG_MARGIN / 2, Constants.DIALOG_MARGIN);
556 		gbc2.insets = new Insets(0, Constants.DIALOG_MARGIN,
557 				Constants.DIALOG_MARGIN / 2, Constants.DIALOG_MARGIN);
558 		body.add(new BoldLabel("State"), gbc1);
559 		body.add(new JLabel(ra.getState().getDisplayName()), gbc2);
560 		gbc1.gridy++;
561 		gbc2.gridy++;
562 		body.add(new BoldLabel("Open"), gbc1);
563 
564 		body.add(new JLabel(DateUtil.getRelativeBuildTime(ra.getCreateDate())), gbc2);
565 
566 		gbc1.gridy++;
567 		gbc2.gridy++;
568 		body.add(new BoldLabel("Author"), gbc1);
569 		body.add(new JLabel(ra.getCreator().getDisplayName()), gbc2);
570 
571 		gbc1.gridy++;
572 		gbc2.gridy++;
573 		body.add(new BoldLabel("Moderator"), gbc1);
574 		body.add(new JLabel(ra.getModerator().getDisplayName()), gbc2);
575 
576 		gbc1.gridy++;
577 		gbc2.gridy++;
578 		body.add(new BoldLabel("Reviewers"), gbc1);
579 
580 		JPanel reviewers = new JPanel();
581 		VerticalFlowLayout layout = new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false);
582 		layout.setHgap(0);
583 		layout.setVgap(0);
584 		Container container = new Container();
585 		layout.layoutContainer(container);
586 
587 		reviewers.setLayout(layout);
588 
589 		Icon reviewCompletedIcon = IconLoader.getIcon("/icons/icn_complete.gif");
590 		try {
591 			for (Reviewer reviewer : ra.getReviewers()) {
592 				JLabel label = new JLabel(reviewer.getDisplayName(),
593 						reviewer.isCompleted() ? reviewCompletedIcon : null,
594 						SwingConstants.LEFT);
595 				label.setOpaque(true);
596 				label.setBackground(Color.WHITE);
597 				label.setHorizontalTextPosition(SwingUtilities.LEFT);
598 				label.setHorizontalAlignment(SwingUtilities.LEFT);
599 				reviewers.add(label);
600 			}
601 
602 			body.add(reviewers, gbc2);
603 		} catch (ValueNotYetInitialized valueNotYetInitialized) {
604 			//do not care
605 		}
606 
607 		gbc1.gridy++;
608 		gbc1.weighty = 1.0;
609 		gbc1.fill = GridBagConstraints.VERTICAL;
610 		JPanel filler = new JPanel();
611 		filler.setOpaque(true);
612 		filler.setBackground(Color.WHITE);
613 		body.add(filler, gbc1);
614 
615 		return body;
616 	}
617 
618 	public static void main(String[] args) {
619 		ServerData cruc = new ServerData("my crucible server", (new ServerId()).toString(), "", "", "");
620 		ReviewBean review = new ReviewBean("myreviewbean");
621 		ReviewAdapter reviewAdapter = new ReviewAdapter(review, cruc);
622 		review.setDescription("My description dfjlslj ldfsjalkfsdjlkj sld"
623 				+ "jldfjal jfdlkjafl jldfsjalfj ldsj fldjsf; ljWojciech Seliga fjdsalkfjs df\nA new line above\nand then some"
624 				+ "very very very long lllllllllllllloooong string.");
625 		review.setName("My review name");
626 		review.setState(State.REVIEW);
627 		review.setCreateDate(new Date());
628 		final ReviewerBean author = new ReviewerBean();
629 		author.setUserName("wseliga");
630 		author.setDisplayName("Wojciech Seliga");
631 		review.setAuthor(author);
632 		review.setCreator(author);
633 		review.setModerator(author);
634 
635 		SwingAppRunner.run(new DetailsPanel(reviewAdapter));
636 	}
637 
638 	public void createdOrEditedVersionedCommentReply(final ReviewAdapter review, final PermId file,
639 			final VersionedComment parentComment,
640 			final
641 			VersionedComment comment) {
642 	}
643 
644 	public void createdOrEditedGeneralCommentReply(final ReviewAdapter review, final GeneralComment parentComment,
645 			final GeneralComment comment) {
646 	}
647 
648 	public void createdOrEditedGeneralComment(final ReviewAdapter review, final GeneralComment comment) {
649 	}
650 
651 	public void createdOrEditedVersionedComment(final ReviewAdapter review, final PermId file, final VersionedComment comment) {
652 	}
653 
654 	public void removedComment(final ReviewAdapter review, final Comment comment) {
655 	}
656 
657 	public void publishedGeneralComment(final ReviewAdapter review, final GeneralComment comment) {
658 	}
659 
660 	public void publishedVersionedComment(final ReviewAdapter review, final PermId filePermId, final VersionedComment comment) {
661 	}
662 
663 	public void refresh() {
664 		final JPanel panel = createBody();
665 		//scroll.setViewport(null);
666 		scroll.setViewportView(panel);
667 		scroll.validate();
668 		validate();
669 	}
670 }