View Javadoc

1   /**
2    * Copyright (C) 2008 Atlassian
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.atlassian.theplugin.idea.crucible;
18  
19  import com.atlassian.theplugin.commons.crucible.CrucibleServerFacade;
20  import com.atlassian.theplugin.commons.crucible.CrucibleServerFacadeImpl;
21  import com.atlassian.theplugin.commons.crucible.ValueNotYetInitialized;
22  import com.atlassian.theplugin.commons.crucible.api.model.Action;
23  import com.atlassian.theplugin.commons.crucible.api.model.*;
24  import com.atlassian.theplugin.commons.exception.ServerPasswordNotProvidedException;
25  import com.atlassian.theplugin.commons.remoteapi.RemoteApiException;
26  import com.atlassian.theplugin.idea.Constants;
27  import com.atlassian.theplugin.idea.IdeaHelper;
28  import com.intellij.ide.BrowserUtil;
29  import com.intellij.openapi.ui.DialogWrapper;
30  import com.intellij.openapi.ui.Messages;
31  import static com.intellij.openapi.ui.Messages.showMessageDialog;
32  import com.intellij.openapi.util.IconLoader;
33  import com.intellij.ui.HyperlinkLabel;
34  import com.intellij.uiDesigner.core.GridConstraints;
35  import com.intellij.uiDesigner.core.GridLayoutManager;
36  import com.intellij.uiDesigner.core.Spacer;
37  import org.jetbrains.annotations.Nullable;
38  
39  import javax.swing.*;
40  import javax.swing.border.Border;
41  import javax.swing.event.HyperlinkEvent;
42  import javax.swing.event.HyperlinkListener;
43  import java.awt.*;
44  
45  
46  public class CrucibleChangeReviewStateForm extends DialogWrapper {
47  	private static final Icon ICON_COMPLETED = IconLoader.getIcon("/icons/icn_complete.gif");
48  
49  	private JPanel rootComponent;
50  	private JPanel detailsPanel;
51  	private JPanel summaryPanel;
52  	private JPanel commentsPanel;
53  	private JCheckBox publishDraftsCheckBox;
54  	private JPanel publishPanel;
55  
56  	private ReviewData review;
57  	private CrucibleServerFacade crucibleServerFacade;
58  	private Action action;
59  	private DescriptionPanel descriptionPanel;
60  
61  	protected CrucibleChangeReviewStateForm(ReviewData review, Action action) {
62  		super(false);
63  		this.review = review;
64  		this.action = action;
65  		this.crucibleServerFacade = CrucibleServerFacadeImpl.getInstance();
66  
67  		$$$setupUI$$$();
68  		init();
69  
70  		publishPanel.setVisible(false);
71  
72  		switch (action) {
73  			case CLOSE:
74  				setTitle("Close review");
75  				getOKAction().putValue(javax.swing.Action.NAME, "Close review...");
76  				break;
77  			case APPROVE:
78  				setTitle("Approve review");
79  				getOKAction().putValue(javax.swing.Action.NAME, "Approve review...");
80  				break;
81  			case SUBMIT:
82  				setTitle("Submit review");
83  				getOKAction().putValue(javax.swing.Action.NAME, "Submit review...");
84  				break;
85  			case ABANDON:
86  				setTitle("Abandon review");
87  				getOKAction().putValue(javax.swing.Action.NAME, "Abandon review...");
88  				break;
89  			case SUMMARIZE:
90  				setTitle("Summarize review");
91  				getOKAction().putValue(javax.swing.Action.NAME, "Summarize review...");
92  				break;
93  			case REOPEN:
94  				setTitle("Reopen review");
95  				getOKAction().putValue(javax.swing.Action.NAME, "Reopen review...");
96  				break;
97  			case RECOVER:
98  				setTitle("Recover abandoned review");
99  				getOKAction().putValue(javax.swing.Action.NAME, "Recover abandoned review...");
100 				break;
101 			case COMPLETE:
102 				setTitle("Complete review");
103 				getOKAction().putValue(javax.swing.Action.NAME, "Complete review...");
104 				publishPanel.setVisible(true);
105 				break;
106 			case UNCOMPLETE:
107 				setTitle("Uncomplete review");
108 				getOKAction().putValue(javax.swing.Action.NAME, "Uncomplete review...");
109 				break;
110 		}
111 
112 		fillReviewInfo(review);
113 	}
114 
115 	private void fillReviewInfo(final ReviewData review) {
116 		getOKAction().setEnabled(false);
117 
118 		new Thread(new Runnable() {
119 			public void run() {
120 				Review reviewInfo = null;
121 				try {
122 					reviewInfo = crucibleServerFacade.getReview(review.getServer(), review.getPermId());
123 				} catch (RemoteApiException e) {
124 					// nothing can be done here
125 				} catch (ServerPasswordNotProvidedException e) {
126 					// nothing can be done here
127 				}
128 				final ReviewData finalReviewInfo = new ReviewDataImpl(reviewInfo, review.getServer());
129 				EventQueue.invokeLater(new Runnable() {
130 					public void run() {
131 						updateReviewInfo(finalReviewInfo);
132 					}
133 				});
134 			}
135 		}, "atlassian-idea-plugin crucible patch upload combos refresh").start();
136 	}
137 
138 	private void updateReviewInfo(ReviewData reviewInfo) {
139 		detailsPanel.add(new DetailsPanel(reviewInfo), BorderLayout.CENTER);
140 		if (Action.CLOSE.equals(action) || !"".equals(reviewInfo.getSummary())) {
141 			descriptionPanel = new DescriptionPanel(review);
142 			summaryPanel.add(descriptionPanel, BorderLayout.CENTER);
143 		}
144 		commentsPanel.add(new CommentsPanel(review), BorderLayout.CENTER);
145 		if (Action.CLOSE.equals(action)) {
146 			descriptionPanel.setEnabled(true);
147 			descriptionPanel.setEditable(true);
148 		} else {
149 			if (!"".equals(reviewInfo.getSummary())) {
150 				descriptionPanel.setEnabled(false);
151 				descriptionPanel.setEditable(false);
152 			}
153 		}
154 		pack();
155 		getOKAction().setEnabled(true);
156 	}
157 
158 	public JComponent getRootComponent() {
159 		return rootComponent;
160 	}
161 
162 	@Nullable
163 	protected JComponent createCenterPanel() {
164 		return getRootComponent();
165 	}
166 
167 	protected void doOKAction() {
168 		try {
169 			switch (action) {
170 				case APPROVE:
171 					crucibleServerFacade.approveReview(review.getServer(), review.getPermId());
172 					break;
173 				case SUBMIT:
174 					crucibleServerFacade.submitReview(review.getServer(), review.getPermId());
175 					break;
176 				case ABANDON:
177 					crucibleServerFacade.abandonReview(review.getServer(), review.getPermId());
178 					break;
179 				case SUMMARIZE:
180 					crucibleServerFacade.summarizeReview(review.getServer(), review.getPermId());
181 					break;
182 				case CLOSE:
183 					crucibleServerFacade.closeReview(review.getServer(), review.getPermId(), descriptionPanel.getText());
184 					break;
185 				case REOPEN:
186 					crucibleServerFacade.reopenReview(review.getServer(), review.getPermId());
187 					break;
188 				case RECOVER:
189 					crucibleServerFacade.recoverReview(review.getServer(), review.getPermId());
190 					break;
191 				case COMPLETE:
192 					if (this.publishDraftsCheckBox.isSelected()) {
193 						crucibleServerFacade.publishAllCommentsForReview(review.getServer(), review.getPermId());
194 					}
195 					crucibleServerFacade.completeReview(review.getServer(), review.getPermId(), true);
196 					break;
197 				case UNCOMPLETE:
198 					crucibleServerFacade.completeReview(review.getServer(), review.getPermId(), false);
199 					break;
200 			}
201 			IdeaHelper.getAppComponent().rescheduleStatusCheckers(true);
202 		} catch (RemoteApiException e) {
203 			showMessageDialog(e.getMessage(),
204 					"Error changing review state: " + review.getServer().getUrl(), Messages.getErrorIcon());
205 		} catch (ServerPasswordNotProvidedException e) {
206 			showMessageDialog(e.getMessage(), "Error changing review state: " + review.getServer().getUrl(),
207 					Messages.getErrorIcon());
208 		}
209 
210 		super.doOKAction();
211 	}
212 
213 	private void createUIComponents() {
214 	}
215 
216 	/**
217 	 * Method generated by IntelliJ IDEA GUI Designer
218 	 * >>> IMPORTANT!! <<<
219 	 * DO NOT edit this method OR call it in your code!
220 	 *
221 	 * @noinspection ALL
222 	 */
223 	private void $$$setupUI$$$() {
224 		rootComponent = new JPanel();
225 		rootComponent.setLayout(new GridLayoutManager(4, 1, new Insets(0, 0, 0, 0), -1, -1));
226 		rootComponent.setMinimumSize(new Dimension(450, 300));
227 		detailsPanel = new JPanel();
228 		detailsPanel.setLayout(new BorderLayout(0, 0));
229 		rootComponent.add(detailsPanel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER,
230 				GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
231 				GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
232 		commentsPanel = new JPanel();
233 		commentsPanel.setLayout(new BorderLayout(0, 0));
234 		rootComponent.add(commentsPanel, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_CENTER,
235 				GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
236 				GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
237 		publishPanel = new JPanel();
238 		publishPanel.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1));
239 		rootComponent.add(publishPanel, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER,
240 				GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
241 				GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
242 		publishDraftsCheckBox = new JCheckBox();
243 		publishDraftsCheckBox.setSelected(true);
244 		publishDraftsCheckBox.setText("Publish all my draft comments");
245 		publishPanel.add(publishDraftsCheckBox, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST,
246 				GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
247 				GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
248 		final Spacer spacer1 = new Spacer();
249 		publishPanel.add(spacer1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL,
250 				1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
251 		summaryPanel = new JPanel();
252 		summaryPanel.setLayout(new BorderLayout(0, 0));
253 		rootComponent.add(summaryPanel, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER,
254 				GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
255 				GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
256 	}
257 
258 	/**
259 	 * @noinspection ALL
260 	 */
261 	public JComponent $$$getRootComponent$$$() {
262 		return rootComponent;
263 	}
264 
265 	private class BoldLabel extends JLabel {
266 		public BoldLabel(String text) {
267 			super(text);
268 			setFont(getFont().deriveFont(Font.BOLD));
269 		}
270 
271 		public BoldLabel() {
272 			this("");
273 		}
274 	}
275 
276 	private class ReviewLabel extends HyperlinkLabel {
277 		ReviewLabel(final ReviewData review) {
278 			super(review.getPermId().getId());
279 			addListener(review.getReviewUrl());
280 		}
281 
282 		private void addListener(final String reviewUrl) {
283 			addHyperlinkListener(new HyperlinkListener() {
284 				public void hyperlinkUpdate(HyperlinkEvent e) {
285 					BrowserUtil.launchBrowser(reviewUrl);
286 				}
287 			});
288 		}
289 	}
290 
291 	private class DetailsPanel extends JPanel {
292 		public DetailsPanel(final ReviewData review) {
293 			JPanel body = new JPanel();
294 
295 			setLayout(new GridBagLayout());
296 			body.setLayout(new GridBagLayout());
297 
298 			GridBagConstraints gbc1 = new GridBagConstraints();
299 			GridBagConstraints gbc2 = new GridBagConstraints();
300 			gbc1.anchor = GridBagConstraints.FIRST_LINE_START;
301 			gbc1.insets = new Insets(0, Constants.DIALOG_MARGIN, 0, Constants.DIALOG_MARGIN);
302 			gbc2.anchor = GridBagConstraints.FIRST_LINE_START;
303 			gbc2.fill = GridBagConstraints.HORIZONTAL;
304 			gbc2.weightx = 1.0;
305 			gbc1.gridx = 0;
306 			gbc2.gridx = 1;
307 			gbc1.gridy = 0;
308 			gbc2.gridy = 0;
309 
310 			body.add(new BoldLabel("Id"), gbc1);
311 			body.add(new ReviewLabel(review), gbc2);
312 			gbc1.gridy++;
313 			gbc2.gridy++;
314 			body.add(new BoldLabel("Name"), gbc1);
315 			body.add(new JLabel(review.getName(), SwingConstants.LEFT), gbc2);
316 			gbc1.gridy++;
317 			gbc2.gridy++;
318 			body.add(new BoldLabel("State"), gbc1);
319 			body.add(new JLabel(review.getState().value(), SwingConstants.LEFT), gbc2);
320 			gbc1.gridy++;
321 			gbc2.gridy++;
322 			body.add(new BoldLabel("Author"), gbc1);
323 			body.add(new JLabel(review.getAuthor().getDisplayName(), SwingConstants.LEFT), gbc2);
324 			gbc1.gridy++;
325 			gbc2.gridy++;
326 			body.add(new BoldLabel("Moderator"), gbc1);
327 			body.add(new JLabel(review.getModerator().getDisplayName(), SwingConstants.LEFT), gbc2);
328 			gbc1.gridy++;
329 			gbc2.gridy++;
330 			body.add(new BoldLabel("Project key"), gbc1);
331 			body.add(new JLabel(review.getProjectKey(), SwingConstants.LEFT), gbc2);
332 			gbc1.gridy++;
333 			gbc2.gridy++;
334 			body.add(new BoldLabel("Repository name"), gbc1);
335 			body.add(new JLabel(review.getRepoName(), SwingConstants.LEFT), gbc2);
336 			gbc1.gridy++;
337 			gbc2.gridy++;
338 			body.add(new BoldLabel("Created"), gbc1);
339 			if (review.getCreateDate() != null) {
340 				body.add(new JLabel(review.getCreateDate().toString()), gbc2);
341 			}
342 			gbc1.gridy++;
343 			gbc2.gridy++;
344 			body.add(new BoldLabel("Closed"), gbc1);
345 			if (review.getCloseDate() != null) {
346 				body.add(new JLabel(review.getCloseDate().toString()), gbc2);
347 			}
348 			gbc1.gridy++;
349 			gbc2.gridy++;
350 
351 			body.add(new BoldLabel("Reviewers"), gbc1);
352 
353 			try {
354 				for (Reviewer reviewer : review.getReviewers()) {
355 					if (reviewer.isCompleted()) {
356 						body.add(new JLabel(reviewer.getDisplayName(), ICON_COMPLETED, SwingConstants.LEFT), gbc2);
357 					} else {
358 						body.add(new JLabel(reviewer.getDisplayName(), SwingConstants.LEFT), gbc2);
359 					}
360 					gbc1.gridy++;
361 					gbc2.gridy++;
362 				}
363 			} catch (ValueNotYetInitialized valueNotYetInitialized) {
364 				valueNotYetInitialized
365 						.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
366 			}
367 
368 
369 			gbc1.gridy++;
370 			gbc1.weighty = 1.0;
371 			gbc1.fill = GridBagConstraints.VERTICAL;
372 			body.add(new JPanel(), gbc1);
373 
374 			GridBagConstraints gbc = new GridBagConstraints();
375 			gbc.gridx = 0;
376 			gbc.gridy = 0;
377 			gbc.weightx = 1.0;
378 			gbc.weighty = 1.0;
379 			gbc.fill = GridBagConstraints.BOTH;
380 			JScrollPane scroll = new JScrollPane(body);
381 			scroll.setBorder(BorderFactory.createEmptyBorder());
382 			add(scroll, gbc);
383 
384 			Border b = BorderFactory.createTitledBorder("Review");
385 			setBorder(b);
386 			Insets i = b.getBorderInsets(this);
387 			setMinimumSize(new Dimension(0, i.top + i.bottom));
388 
389 		}
390 	}
391 
392 	private class CommentsPanel extends JPanel {
393 		public CommentsPanel(final ReviewData review) {
394 			JPanel body = new JPanel();
395 
396 			setLayout(new GridBagLayout());
397 			body.setLayout(new GridBagLayout());
398 
399 			GridBagConstraints gbc1 = new GridBagConstraints();
400 			GridBagConstraints gbc2 = new GridBagConstraints();
401 			gbc1.anchor = GridBagConstraints.FIRST_LINE_START;
402 			gbc1.insets = new Insets(0, Constants.DIALOG_MARGIN, 0, Constants.DIALOG_MARGIN);
403 			gbc2.anchor = GridBagConstraints.FIRST_LINE_START;
404 			gbc2.fill = GridBagConstraints.HORIZONTAL;
405 			gbc2.weightx = 1.0;
406 			gbc1.gridx = 0;
407 			gbc2.gridx = 1;
408 			gbc1.gridy = 0;
409 			gbc2.gridy = 0;
410 
411 			String userName = review.getServer().getUsername();
412 			int generalComments = 0;
413 			int versionedComments = 0;
414 			int myGeneralComments = 0;
415 			int myVersionedComments = 0;
416 			int myDefects = 0;
417 			int myDrafts = 0;
418 			int allDefects = 0;
419 			int allDrafts = 0;
420 			try {
421 				generalComments = review.getGeneralComments().size();
422 				versionedComments = review.getVersionedComments().size();
423 				for (GeneralComment generalComment : review.getGeneralComments()) {
424 					if (generalComment.getAuthor().equals(userName)) {
425 						myGeneralComments++;
426 						if (generalComment.isDraft()) {
427 							myDrafts++;
428 							allDrafts++;
429 						}
430 						if (generalComment.isDefectRaised()) {
431 							myDefects++;
432 							allDefects++;
433 						}
434 					} else {
435 						if (generalComment.isDraft()) {
436 							allDrafts++;
437 						}
438 						if (generalComment.isDefectRaised()) {
439 							allDefects++;
440 						}
441 					}
442 
443 				}
444 				for (VersionedComment comment : review.getVersionedComments()) {
445 					if (comment.getAuthor().equals(userName)) {
446 						myVersionedComments++;
447 						if (comment.isDraft()) {
448 							myDrafts++;
449 							allDrafts++;
450 						}
451 						if (comment.isDefectRaised()) {
452 							myDefects++;
453 							allDefects++;
454 						}
455 					} else {
456 						if (comment.isDraft()) {
457 							allDrafts++;
458 						}
459 						if (comment.isDefectRaised()) {
460 							allDefects++;
461 						}
462 					}
463 
464 				}
465 			} catch (ValueNotYetInitialized valueNotYetInitialized) {
466 				valueNotYetInitialized
467 						.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
468 			}
469 
470 
471 			body.add(new BoldLabel("My draft comments"), gbc1);
472 			body.add(new JLabel(Integer.toString(myDrafts), SwingConstants.LEFT), gbc2);
473 			gbc1.gridy++;
474 			gbc2.gridy++;
475 
476 			body.add(new BoldLabel("All my comments"), gbc1);
477 			body.add(new JLabel(Integer.toString(myGeneralComments + myVersionedComments), SwingConstants.LEFT), gbc2);
478 			gbc1.gridy++;
479 			gbc2.gridy++;
480 
481 			body.add(new BoldLabel("Total comments"), gbc1);
482 			body.add(new JLabel(Integer.toString(generalComments + versionedComments), SwingConstants.LEFT), gbc2);
483 			gbc1.gridy++;
484 			gbc2.gridy++;
485 
486 			body.add(new BoldLabel("Total defects"), gbc1);
487 			body.add(new JLabel(Integer.toString(allDefects), SwingConstants.LEFT), gbc2);
488 			gbc1.gridy++;
489 			gbc2.gridy++;
490 
491 			body.add(new BoldLabel("My defects comments"), gbc1);
492 			body.add(new JLabel(Integer.toString(myDefects), SwingConstants.LEFT), gbc2);
493 			gbc1.gridy++;
494 			gbc2.gridy++;
495 
496 			gbc1.gridy++;
497 			gbc1.weighty = 1.0;
498 			gbc1.fill = GridBagConstraints.VERTICAL;
499 			body.add(new JPanel(), gbc1);
500 
501 			GridBagConstraints gbc = new GridBagConstraints();
502 			gbc.gridx = 0;
503 			gbc.gridy = 0;
504 			gbc.weightx = 1.0;
505 			gbc.weighty = 1.0;
506 			gbc.fill = GridBagConstraints.BOTH;
507 			JScrollPane scroll = new JScrollPane(body);
508 			scroll.setBorder(BorderFactory.createEmptyBorder());
509 			add(scroll, gbc);
510 
511 			Border b = BorderFactory.createTitledBorder("Comments");
512 			setBorder(b);
513 			Insets i = b.getBorderInsets(this);
514 			setMinimumSize(new Dimension(0, i.top + i.bottom));
515 
516 		}
517 	}
518 
519 	private class DescriptionPanel extends JPanel {
520 		JEditorPane body = new JEditorPane();
521 
522 		public DescriptionPanel(final ReviewData review) {
523 			setLayout(new GridBagLayout());
524 			GridBagConstraints gbc = new GridBagConstraints();
525 
526 			gbc.gridx = 0;
527 			gbc.gridy = 0;
528 
529 			gbc.insets = new Insets(0, 0, 0, 0);
530 			gbc.fill = GridBagConstraints.BOTH;
531 			gbc.weightx = 1.0;
532 			gbc.weighty = 1.0;
533 
534 			JScrollPane sp = new JScrollPane(body,
535 					ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
536 					ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
537 			sp.setBorder(BorderFactory.createEmptyBorder());
538 			sp.setOpaque(false);
539 			body.setEditable(false);
540 			body.addHyperlinkListener(new HyperlinkListener() {
541 				public void hyperlinkUpdate(HyperlinkEvent e) {
542 					if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
543 						BrowserUtil.launchBrowser(e.getURL().toString());
544 					}
545 				}
546 			});
547 
548 			body.setOpaque(false);
549 			body.setBorder(BorderFactory.createEmptyBorder());
550 			//body.setContentType("text/html");
551 			//body.setText("<html><head></head><body>" + review.getSummary() + "</body></html>");
552 			body.setText(review.getSummary());
553 			sp.getViewport().setOpaque(false);
554 			body.setCaretPosition(0);
555 			add(sp, gbc);
556 
557 			Border b = BorderFactory.createTitledBorder("Review summary");
558 			setBorder(b);
559 			Insets i = b.getBorderInsets(this);
560 			int minHeight = i.top + i.bottom;
561 			setMinimumSize(new Dimension(0, minHeight));
562 		}
563 
564 		public void setEditable(boolean editable) {
565 			body.setEditable(editable);
566 		}
567 
568 		public void setEnabled(boolean enabled) {
569 			body.setEnabled(enabled);
570 		}
571 
572 		public String getText() {
573 			return body.getText();
574 		}
575 	}
576 }