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.cfg.CfgUtil;
19  import com.atlassian.theplugin.commons.cfg.CrucibleServerCfg;
20  import com.atlassian.theplugin.commons.cfg.ProjectConfiguration;
21  import com.atlassian.theplugin.commons.crucible.CrucibleServerFacade;
22  import com.atlassian.theplugin.commons.crucible.ValueNotYetInitialized;
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.commons.remoteapi.ServerData;
27  import com.atlassian.theplugin.commons.util.MiscUtil;
28  import com.atlassian.theplugin.crucible.model.UpdateReason;
29  import com.atlassian.theplugin.idea.IdeaHelper;
30  import com.atlassian.theplugin.idea.config.ProjectCfgManagerImpl;
31  import com.atlassian.theplugin.idea.crucible.comboitems.RepositoryComboBoxItem;
32  import com.atlassian.theplugin.idea.ui.DialogWithDetails;
33  import com.atlassian.theplugin.util.PluginUtil;
34  import com.intellij.openapi.application.ApplicationManager;
35  import com.intellij.openapi.application.ModalityState;
36  import com.intellij.openapi.progress.ProgressIndicator;
37  import com.intellij.openapi.progress.ProgressManager;
38  import com.intellij.openapi.progress.Task;
39  import com.intellij.openapi.project.Project;
40  import com.intellij.openapi.ui.DialogWrapper;
41  import com.intellij.openapi.ui.Messages;
42  import com.intellij.ui.ListSpeedSearch;
43  import com.jgoodies.forms.layout.CellConstraints;
44  import com.jgoodies.forms.layout.FormLayout;
45  import org.apache.commons.lang.StringUtils;
46  import org.jetbrains.annotations.NotNull;
47  import org.jetbrains.annotations.Nullable;
48  
49  import static javax.swing.Action.NAME;
50  import javax.swing.*;
51  import javax.swing.event.DocumentEvent;
52  import javax.swing.event.DocumentListener;
53  import java.awt.*;
54  import java.awt.event.ActionEvent;
55  import java.awt.event.ActionListener;
56  import java.awt.event.MouseAdapter;
57  import java.awt.event.MouseEvent;
58  import java.util.*;
59  import java.util.List;
60  
61  
62  public abstract class CrucibleReviewCreateForm extends DialogWrapper {
63  	private JPanel rootComponent;
64  	private JTextField titleText;
65  	private JComboBox crucibleServersComboBox;
66  	private JTextArea statementArea;
67  	private JComboBox repoComboBox;
68  	private JComboBox projectsComboBox;
69  	private JComboBox authorComboBox;
70  	private JComboBox moderatorComboBox;
71  	private JList reviewersList;
72  	private JCheckBox allowCheckBox;
73  	private JCheckBox leaveAsDraftCheckBox;
74  	private JPanel customComponentPanel;
75  	private JLabel repositoryLabel;
76  	private JLabel selectedReviewers;
77  	private DefaultListModel model;
78  	private UserListCellRenderer cellRenderer = new UserListCellRenderer();
79  
80  	protected Project project;
81  	protected CrucibleServerFacade crucibleServerFacade;
82  	private final ProjectCfgManagerImpl projectCfgManager;
83  	private int reviewCreationTimeout = -1;
84  	private static final int MILLISECONDS_IN_MINUTE = 1000 * 60;
85  
86  	protected void setCustomComponent(JComponent component) {
87  		customComponentPanel.removeAll();
88  		if (component != null) {
89  			customComponentPanel.add(component);
90  			customComponentPanel.validate();
91  		}
92  	}
93  
94  	public CrucibleReviewCreateForm(Project project, CrucibleServerFacade crucibleServerFacade, String commitMessage,
95  			@NotNull final ProjectCfgManagerImpl projectCfgManager, @NotNull String dialogTitle) {
96  		super(false);
97  		this.project = project;
98  		this.crucibleServerFacade = crucibleServerFacade;
99  		this.projectCfgManager = projectCfgManager;
100 		setTitle(dialogTitle);
101 
102 		$$$setupUI$$$();
103 		init();
104 
105 		if (!shouldShowRepo()) {
106 			repositoryLabel.setVisible(false);
107 			repoComboBox.setVisible(false);
108 		}
109 
110 		customComponentPanel.setLayout(new BorderLayout());
111 		titleText.setText(commitMessage);
112 		getOKAction().putValue(NAME, "Create review...");
113 		crucibleServersComboBox.addActionListener(new ActionListener() {
114 			public void actionPerformed(ActionEvent e) {
115 				if (crucibleServersComboBox.getItemCount() > 0 && crucibleServersComboBox.getSelectedItem() != null &&
116 						crucibleServersComboBox.getSelectedItem() instanceof ServerComboBoxItem) {
117 					final ServerComboBoxItem boxItem = (ServerComboBoxItem) crucibleServersComboBox.getSelectedItem();
118 					fillServerRelatedCombos(boxItem.getServer());
119 				}
120 			}
121 		});
122 
123 		reviewersList.addMouseListener(new MouseAdapter() {
124 			@Override
125 			public void mousePressed(MouseEvent e) {
126 				int index = reviewersList.locationToIndex(e.getPoint());
127 				setCheckboxState(index);
128 				refreshUserModel();
129 				reviewersList.setSelectedIndex(index);
130 			}
131 		});
132 
133 		new ListSpeedSearch(reviewersList) {
134 			@Override
135 			protected boolean compare(final String s, final String s1) {
136 				return s != null && s1 != null ? s.toUpperCase().contains(s1.toUpperCase()) : super.compare(s, s1);
137 			}
138 		};
139 
140 //		reviewersList.addKeyListener(new KeyAdapter() {
141 //			@Override
142 //			public void keyPressed(KeyEvent e) {
143 //				if (e.getKeyCode() == KeyEvent.VK_SPACE) {
144 //					int index = reviewersList.getSelectedIndex();
145 //					setCheckboxState(index);
146 //					refreshUserModel();
147 //					reviewersList.setSelectedIndex(index);
148 //				}
149 //			}
150 //		});
151 
152 		moderatorComboBox.addActionListener(new ActionListener() {
153 
154 			public void actionPerformed(final ActionEvent event) {
155 
156 				if (moderatorComboBox.getSelectedItem() instanceof UserComboBoxItem) {
157 					refreshUserModel();
158 				}
159 			}
160 		});
161 
162 		ActionListener enableOkActionListener = new ActionListener() {
163 
164 			public void actionPerformed(final ActionEvent event) {
165 				getOKAction().setEnabled(isValidForm());
166 			}
167 		};
168 
169 		authorComboBox.addActionListener(enableOkActionListener);
170 		repoComboBox.addActionListener(enableOkActionListener);
171 		projectsComboBox.addActionListener(enableOkActionListener);
172 		crucibleServersComboBox.addActionListener(enableOkActionListener);
173 		titleText.getDocument().addDocumentListener(new DocumentListener() {
174 			public void insertUpdate(final DocumentEvent e) {
175 				getOKAction().setEnabled(isValidForm());
176 			}
177 
178 			public void removeUpdate(final DocumentEvent e) {
179 				getOKAction().setEnabled(isValidForm());
180 			}
181 
182 			public void changedUpdate(final DocumentEvent e) {
183 				getOKAction().setEnabled(isValidForm());
184 			}
185 		});
186 
187 		fillInCrucibleServers();
188 	}
189 
190 	private void refreshUserModel() {
191 		final Object selectedItem = moderatorComboBox.getSelectedItem();
192 		if (selectedItem instanceof UserComboBoxItem) {
193 			final UserComboBoxItem userComboBoxItem = (UserComboBoxItem) selectedItem;
194 			User moderatorUser = userComboBoxItem.getUser();
195 			final ArrayList<User> disabledUsers = new ArrayList<User>();
196 			disabledUsers.add(moderatorUser);
197 			cellRenderer.setDisabledUsers(disabledUsers);
198 			for (int i = 0; i < model.size(); i++) {
199 				UserListItem reviewer = (UserListItem) model.get(i);
200 				if (reviewer.getUser().equals(moderatorUser)) {
201 					reviewer.setSelected(false);
202 				}
203 			}
204 			reviewersList.setModel(model);
205 
206 			Collection<UserListItem> displayedSelectedUsers = new ArrayList<UserListItem>();
207 			Collection<UserListItem> allSelectedUsers = new ArrayList<UserListItem>();
208 			for (int i = 0; i < reviewersList.getModel().getSize(); ++i) {
209 				UserListItem user = (UserListItem) reviewersList.getModel().getElementAt(i);
210 				if (user.isSelected()) {
211 					allSelectedUsers.add(user);
212 					displayedSelectedUsers.add(user);
213 
214 					String displayStr = prepareSelectedReviewersString(displayedSelectedUsers, allSelectedUsers);
215 					int displayStrWidth = selectedReviewers.getFontMetrics(selectedReviewers.getFont()).stringWidth(displayStr);
216 
217 					if (displayStrWidth > reviewersList.getWidth()) {
218 						displayedSelectedUsers.remove(user);
219 					}
220 				}
221 			}
222 
223 			if (displayedSelectedUsers.size() == 0) {
224 				this.selectedReviewers.setText("None");
225 				this.selectedReviewers.setToolTipText(null);
226 			} else {
227 				String labelText = prepareSelectedReviewersString(displayedSelectedUsers, allSelectedUsers);
228 				if (displayedSelectedUsers.size() < allSelectedUsers.size()) {
229 					labelText += " ...";
230 				}
231 				this.selectedReviewers.setText(labelText);
232 				this.selectedReviewers.setToolTipText(prepareSelectedReviewersTooltip(allSelectedUsers));
233 			}
234 
235 			reviewersList.revalidate();
236 			reviewersList.repaint();
237 		}
238 		getOKAction().setEnabled(isValidForm());
239 	}
240 
241 	private String prepareSelectedReviewersTooltip(final Collection<UserListItem> selectedUsersTooltip) {
242 		StringBuilder ret = new StringBuilder("<html>");
243 
244 		for (UserListItem user : selectedUsersTooltip) {
245 			ret.append(user.getUser().getDisplayName());
246 			ret.append("<br>");
247 		}
248 
249 		ret.append("</html>");
250 
251 		return ret.toString();
252 	}
253 
254 	private String prepareSelectedReviewersString(final Collection<UserListItem> selectedUsersLabel,
255 			final Collection<UserListItem> allSelectedUsers) {
256 		return "(" + allSelectedUsers.size() + " of " + reviewersList.getModel().getSize() + ") "
257 				+ StringUtils.join(selectedUsersLabel, ", ");
258 	}
259 
260 	private void setCheckboxState(int index) {
261 		if (index != -1) {
262 			UserListItem pi = (UserListItem) reviewersList.getModel().getElementAt(index);
263 			pi.setSelected(!pi.isSelected());
264 //			setViewState(index, pi.isSelected());
265 			repaint();
266 		}
267 	}
268 
269 	// CHECKSTYLE:ON
270 
271 	@Override
272 	public JComponent getPreferredFocusedComponent() {
273 		return titleText;
274 	}
275 
276 	/**
277 	 * Method generated by IntelliJ IDEA GUI Designer
278 	 * >>> IMPORTANT!! <<<
279 	 * DO NOT edit this method OR call it in your code!
280 	 *
281 	 * @noinspection ALL
282 	 */
283 	private void $$$setupUI$$$() {
284 		createUIComponents();
285 		rootComponent = new JPanel();
286 		rootComponent.setLayout(new FormLayout("fill:d:grow",
287 				"center:max(d;4px):noGrow,top:3dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow,center:max(d;4px):noGrow,center:p:grow,top:3dlu:noGrow,fill:d:noGrow,top:3dlu:noGrow,center:max(d;4px):noGrow"));
288 		rootComponent.setMinimumSize(new Dimension(800, 505));
289 		final JLabel label1 = new JLabel();
290 		label1.setText("Title:");
291 		CellConstraints cc = new CellConstraints();
292 		rootComponent.add(label1, cc.xy(1, 1));
293 		titleText = new JTextField();
294 		rootComponent.add(titleText, cc.xy(1, 3, CellConstraints.FILL, CellConstraints.DEFAULT));
295 		final JPanel panel1 = new JPanel();
296 		panel1.setLayout(new FormLayout(
297 				"fill:d:noGrow,left:4dlu:noGrow,fill:300px:grow,left:4dlu:noGrow,fill:max(d;4px):noGrow,left:4dlu:noGrow,fill:max(p;4px):grow",
298 				"center:d:noGrow,top:3dlu:noGrow,center:max(d;4px):noGrow,top:3dlu:noGrow,center:max(d;4px):noGrow,top:3dlu:noGrow,center:max(d;4px):noGrow,top:3dlu:noGrow,center:max(d;4px):noGrow,top:4dlu:noGrow,center:max(d;4px):noGrow"));
299 		rootComponent.add(panel1, cc.xy(1, 5));
300 		final JLabel label2 = new JLabel();
301 		label2.setText("Server:");
302 		panel1.add(label2, cc.xy(1, 1, CellConstraints.DEFAULT, CellConstraints.CENTER));
303 		crucibleServersComboBox = new JComboBox();
304 		panel1.add(crucibleServersComboBox, cc.xy(3, 1));
305 		final JLabel label3 = new JLabel();
306 		label3.setInheritsPopupMenu(false);
307 		label3.setText("Project:");
308 		panel1.add(label3, cc.xy(1, 3, CellConstraints.DEFAULT, CellConstraints.CENTER));
309 		projectsComboBox = new JComboBox();
310 		panel1.add(projectsComboBox, cc.xy(3, 3));
311 		repositoryLabel = new JLabel();
312 		repositoryLabel.setText("Repository:");
313 		panel1.add(repositoryLabel, cc.xy(1, 5, CellConstraints.DEFAULT, CellConstraints.CENTER));
314 		final JLabel label4 = new JLabel();
315 		label4.setText("Moderator:");
316 		panel1.add(label4, cc.xy(1, 7, CellConstraints.DEFAULT, CellConstraints.CENTER));
317 		final JLabel label5 = new JLabel();
318 		label5.setText("Author:");
319 		panel1.add(label5, cc.xy(1, 9, CellConstraints.DEFAULT, CellConstraints.CENTER));
320 		repoComboBox = new JComboBox();
321 		panel1.add(repoComboBox, cc.xy(3, 5));
322 		moderatorComboBox = new JComboBox();
323 		panel1.add(moderatorComboBox, cc.xy(3, 7));
324 		authorComboBox = new JComboBox();
325 		panel1.add(authorComboBox, cc.xy(3, 9));
326 		final JPanel panel2 = new JPanel();
327 		panel2.setLayout(new BorderLayout(0, 0));
328 		panel1.add(panel2, cc.xywh(7, 1, 1, 7, CellConstraints.DEFAULT, CellConstraints.FILL));
329 		final JScrollPane scrollPane1 = new JScrollPane();
330 		panel2.add(scrollPane1, BorderLayout.CENTER);
331 		scrollPane1.setViewportView(reviewersList);
332 		final JLabel label6 = new JLabel();
333 		label6.setText("Reviewers: ");
334 		panel1.add(label6, cc.xy(5, 1, CellConstraints.RIGHT, CellConstraints.TOP));
335 		allowCheckBox = new JCheckBox();
336 		allowCheckBox.setEnabled(true);
337 		allowCheckBox.setText("Allow anyone to join");
338 		panel1.add(allowCheckBox, cc.xy(7, 11));
339 		final JLabel label7 = new JLabel();
340 		label7.setText("Selected: ");
341 		panel1.add(label7, cc.xy(5, 9, CellConstraints.RIGHT, CellConstraints.DEFAULT));
342 		selectedReviewers = new JLabel();
343 		selectedReviewers.setHorizontalTextPosition(2);
344 		selectedReviewers.setText("None");
345 		panel1.add(selectedReviewers, cc.xy(7, 9, CellConstraints.LEFT, CellConstraints.DEFAULT));
346 		final JLabel label8 = new JLabel();
347 		label8.setText("Statement of Objectives:");
348 		rootComponent.add(label8, cc.xy(1, 7));
349 		final JScrollPane scrollPane2 = new JScrollPane();
350 		rootComponent.add(scrollPane2, cc.xy(1, 9, CellConstraints.FILL, CellConstraints.FILL));
351 		statementArea = new JTextArea();
352 		statementArea.setLineWrap(true);
353 		statementArea.setRows(5);
354 		scrollPane2.setViewportView(statementArea);
355 		customComponentPanel = new JPanel();
356 		customComponentPanel.setLayout(new BorderLayout(0, 0));
357 		rootComponent.add(customComponentPanel, cc.xy(1, 11, CellConstraints.DEFAULT, CellConstraints.FILL));
358 		leaveAsDraftCheckBox = new JCheckBox();
359 		leaveAsDraftCheckBox.setText("Save review as Draft");
360 		rootComponent.add(leaveAsDraftCheckBox, cc.xy(1, 13));
361 		label1.setLabelFor(titleText);
362 		label2.setLabelFor(crucibleServersComboBox);
363 		label5.setLabelFor(scrollPane1);
364 		label8.setLabelFor(statementArea);
365 	}
366 
367 	/**
368 	 * @noinspection ALL
369 	 */
370 	public JComponent $$$getRootComponent$$$() {
371 		return rootComponent;
372 	}
373 
374 	// CHECKSTYLE:OFF
375 
376 	private static final class ServerComboBoxItem {
377 		private final ServerData server;
378 
379 		private ServerComboBoxItem(ServerData server) {
380 			this.server = server;
381 		}
382 
383 		@Override
384 		public String toString() {
385 			return server.getName();
386 		}
387 
388 		public ServerData getServer() {
389 			return server;
390 		}
391 
392 		@Override
393 		public boolean equals(final Object o) {
394 			if (this == o) {
395 				return true;
396 			}
397 			if (!(o instanceof ServerComboBoxItem)) {
398 				return false;
399 			}
400 
401 			final ServerComboBoxItem boxItem = (ServerComboBoxItem) o;
402 
403 			//noinspection RedundantIfStatement
404 			if (!server.equals(boxItem.server)) {
405 				return false;
406 			}
407 
408 			return true;
409 		}
410 
411 		@Override
412 		public int hashCode() {
413 			return server.hashCode();
414 		}
415 	}
416 
417 	private static final class ProjectComboBoxItem {
418 		private final CrucibleProject wrappedProject;
419 
420 		private ProjectComboBoxItem(@NotNull final CrucibleProject project) {
421 			this.wrappedProject = project;
422 		}
423 
424 		@Override
425 		public String toString() {
426 			return wrappedProject.getName();
427 		}
428 
429 		public CrucibleProject getWrappedProject() {
430 			return wrappedProject;
431 		}
432 
433 		@Override
434 		public boolean equals(final Object o) {
435 			if (this == o) {
436 				return true;
437 			}
438 			if (o == null || getClass() != o.getClass()) {
439 				return false;
440 			}
441 
442 			final ProjectComboBoxItem that = (ProjectComboBoxItem) o;
443 
444 			//noinspection RedundantIfStatement
445 			if (!wrappedProject.equals(that.wrappedProject)) {
446 				return false;
447 			}
448 
449 			return true;
450 		}
451 
452 		@Override
453 		public int hashCode() {
454 			return wrappedProject.hashCode();
455 		}
456 	}
457 
458 	private static final class UserComboBoxItem {
459 		private final User user;
460 
461 		private UserComboBoxItem(User user) {
462 			this.user = user;
463 		}
464 
465 		@Override
466 		public String toString() {
467 			return user.getDisplayName();
468 		}
469 
470 		public User getUser() {
471 			return user;
472 		}
473 	}
474 
475 
476 	private void fillInCrucibleServers() {
477 		final Collection<CrucibleServerCfg> enabledServers = projectCfgManager.getCfgManager().
478 				getAllEnabledCrucibleServers(CfgUtil.getProjectId(project));
479 		if (enabledServers.isEmpty()) {
480 			crucibleServersComboBox.setEnabled(false);
481 			crucibleServersComboBox.addItem("Enable a Crucible server first!");
482 			getOKAction().setEnabled(false);
483 		} else {
484 			for (CrucibleServerCfg server : enabledServers) {
485 				crucibleServersComboBox.addItem(new ServerComboBoxItem(projectCfgManager.getServerData(server)));
486 			}
487 			final ServerData defCrucServer = projectCfgManager.getDefaultCrucibleServer();
488 			if (defCrucServer != null) {
489 				crucibleServersComboBox.setSelectedItem(new ServerComboBoxItem(defCrucServer));
490 			}
491 		}
492 	}
493 
494 	private void fillServerRelatedCombos(final ServerData server) {
495 		projectsComboBox.removeAllItems();
496 		if (shouldShowRepo()) {
497 			repoComboBox.removeAllItems();
498 		}
499 		authorComboBox.removeAllItems();
500 		moderatorComboBox.removeAllItems();
501 		model.removeAllElements();
502 		getOKAction().setEnabled(false);
503 
504 		final CrucibleServerData data = crucibleData.get(server.getServerId());
505 
506 		if (data == null) {
507 			new Thread(new Runnable() {
508 				public void run() {
509 					List<CrucibleProject> projects = new ArrayList<CrucibleProject>();
510 					List<Repository> repositories = new ArrayList<Repository>();
511 					List<User> users = new ArrayList<User>();
512 
513 					try {
514 						projects = crucibleServerFacade.getProjects(server);
515 						if (shouldShowRepo()) {
516 							repositories = crucibleServerFacade.getRepositories(server);
517 						}
518 						users = crucibleServerFacade.getUsers(server);
519 					} catch (final Exception e) {
520 						if (CrucibleReviewCreateForm.this.getRootComponent().isShowing()) {
521 							ApplicationManager.getApplication().invokeAndWait(new Runnable() {
522 								public void run() {
523 									DialogWithDetails.showExceptionDialog(project, "Cannot retrieve data from Crucible server",
524 											e);
525 								}
526 							}, ModalityState.stateForComponent(CrucibleReviewCreateForm.this.getRootComponent()));
527 						}
528 					}
529 					final CrucibleServerData crucibleServerData = new CrucibleServerData(repositories, projects, users);
530 					crucibleData.put(server.getServerId(), crucibleServerData);
531 					EventQueue.invokeLater(new Runnable() {
532 						public void run() {
533 							updateServerRelatedCombos(server, crucibleServerData);
534 						}
535 					});
536 				}
537 			}, "atlassian-idea-plugin crucible patch upload combos refresh").start();
538 		} else {
539 			updateServerRelatedCombos(server, data);
540 
541 		}
542 	}
543 
544 
545 	protected static class CrucibleServerData {
546 		private final List<CrucibleProject> projects;
547 
548 		private final List<Repository> repositories;
549 
550 		private final List<User> users;
551 
552 		public CrucibleServerData(final List<Repository> repositories, final List<CrucibleProject> projects,
553 				final List<User> users) {
554 			this.repositories = repositories;
555 			this.projects = projects;
556 			this.users = users;
557 		}
558 
559 		public List<CrucibleProject> getProjects() {
560 			return projects;
561 		}
562 
563 		public List<Repository> getRepositories() {
564 			return repositories;
565 		}
566 
567 		public List<User> getUsers() {
568 			return users;
569 		}
570 	}
571 
572 	private Map<String, CrucibleServerData> crucibleData = MiscUtil.buildConcurrentHashMap(5);
573 
574 
575 	private void updateServerRelatedCombos(final ServerData server, final CrucibleServerData crucibleServerData) {
576 
577 		final ServerComboBoxItem selectedItem = (ServerComboBoxItem) crucibleServersComboBox.getSelectedItem();
578 		if (selectedItem == null || !selectedItem.getServer().equals(server)) {
579 			return;
580 		}
581 
582 		// we are doing here once more, as it's executed by a separate thread and meantime
583 		// the combos could have been populated by another thread
584 		projectsComboBox.removeAllItems();
585 		if (shouldShowRepo()) {
586 			repoComboBox.removeAllItems();
587 		}
588 		authorComboBox.removeAllItems();
589 		moderatorComboBox.removeAllItems();
590 		model.removeAllElements();
591 
592 		ProjectConfiguration prjCfg = projectCfgManager.getCfgManager().getProjectConfiguration(CfgUtil.getProjectId(project));
593 		if (crucibleServerData.getProjects().isEmpty()) {
594 			projectsComboBox.setEnabled(false);
595 			projectsComboBox.addItem("No projects");
596 			getOKAction().setEnabled(false);
597 		} else {
598 			projectsComboBox.setEnabled(true);
599 			for (CrucibleProject myProject : crucibleServerData.getProjects()) {
600 				projectsComboBox.addItem(new ProjectComboBoxItem(myProject));
601 			}
602 
603 			// setting default project if such is defined
604 			if (prjCfg != null) {
605 				final String defaultProjectKey = prjCfg.getDefaultCrucibleProject();
606 				if (defaultProjectKey != null) {
607 					for (int i = 0; i < projectsComboBox.getItemCount(); ++i) {
608 						if (projectsComboBox.getItemAt(i) instanceof ProjectComboBoxItem) {
609 							if (((ProjectComboBoxItem) projectsComboBox.getItemAt(i)).getWrappedProject().getKey()
610 									.equals(defaultProjectKey)) {
611 								projectsComboBox.setSelectedIndex(i);
612 								break;
613 							}
614 						}
615 					}
616 				}
617 			}
618 		}
619 
620 		if (shouldShowRepo()) {
621 			repoComboBox.addItem(""); // repo is not required for instance for patch review
622 			if (!crucibleServerData.getRepositories().isEmpty()) {
623 				for (Repository repo : crucibleServerData.getRepositories()) {
624 					repoComboBox.addItem(new RepositoryComboBoxItem(repo));
625 				}
626 
627 				// setting default repo if such is defined
628 
629 				if (prjCfg != null) {
630 					final String defaultRepo = prjCfg.getDefaultCrucibleRepo();
631 					if (defaultRepo != null) {
632 						for (int i = 0; i < repoComboBox.getItemCount(); ++i) {
633 							if (repoComboBox.getItemAt(i) instanceof RepositoryComboBoxItem) {
634 								if (((RepositoryComboBoxItem) repoComboBox.getItemAt(i)).getRepository().getName()
635 										.equals(defaultRepo)) {
636 									repoComboBox.setSelectedIndex(i);
637 									break;
638 								}
639 							}
640 						}
641 					}
642 				}
643 				getOKAction().setEnabled(true);
644 			}
645 			// if only one repository
646 			if (shouldAutoSelectRepo(crucibleServerData)) {
647 				repoComboBox.setSelectedIndex(repoComboBox.getItemCount() - 1);
648 			}
649 		}
650 		authorComboBox.addItem("");
651 		moderatorComboBox.addItem("");
652 		if (!crucibleServerData.getUsers().isEmpty()) {
653 			int indexToSelect = -1;
654 			int index = 0;
655 			for (User user : crucibleServerData.getUsers()) {
656 				authorComboBox.addItem(new UserComboBoxItem(user));
657 				moderatorComboBox.addItem(new UserComboBoxItem(user));
658 				if (user.getUserName().equals(server.getUserName())) {
659 					indexToSelect = index + 1;
660 				}
661 
662 				model.addElement(new UserListItem(user, false));
663 				index++;
664 			}
665 			if (indexToSelect != -1) {
666 				authorComboBox.setSelectedIndex(indexToSelect);
667 				moderatorComboBox.setSelectedIndex(indexToSelect);
668 			}
669 		}
670 
671 		getOKAction().setEnabled(isValidForm());
672 
673 	}
674 
675 	protected boolean shouldShowRepo() {
676 		return true;
677 	}
678 
679 
680 	protected boolean shouldAutoSelectRepo(CrucibleServerData crucibleServerData) {
681 		return false;
682 	}
683 
684 	public JComponent getRootComponent() {
685 		return rootComponent;
686 	}
687 
688 	@Override
689 	@Nullable
690 	protected JComponent createCenterPanel() {
691 		return getRootComponent();
692 	}
693 
694 	protected class ReviewProvider extends ReviewBean {
695 		private final ServerData server;
696 
697 		public ReviewProvider(ServerData server) {
698 			super(server.getUrl());
699 			this.server = server;
700 		}
701 
702 		@NotNull
703 		@Override
704 		public User getAuthor() {
705 			if (authorComboBox.getSelectedItem() instanceof UserComboBoxItem) {
706 				return ((UserComboBoxItem) authorComboBox.getSelectedItem()).getUser();
707 			} else {
708 				return null;
709 			}
710 		}
711 
712 		@Override
713 		public User getCreator() {
714 			UserBean user = new UserBean();
715 			user.setUserName(server.getUserName());
716 			return user;
717 		}
718 
719 		@Override
720 		public String getDescription() {
721 			return statementArea.getText();
722 		}
723 
724 		@NotNull
725 		@Override
726 		public User getModerator() {
727 			if (moderatorComboBox.getSelectedItem() instanceof UserComboBoxItem) {
728 				return ((UserComboBoxItem) moderatorComboBox.getSelectedItem()).getUser();
729 			} else {
730 				return null;
731 			}
732 		}
733 
734 		@Override
735 		public String getName() {
736 			return titleText.getText();
737 		}
738 
739 		@Nullable
740 		@Override
741 		public PermId getParentReview() {
742 			return null;
743 		}
744 
745 		@Nullable
746 		@Override
747 		public PermId getPermId() {
748 			return null;
749 		}
750 
751 		@NotNull
752 		@Override
753 		public String getProjectKey() {
754 			return ((ProjectComboBoxItem) projectsComboBox.getSelectedItem()).getWrappedProject().getKey();
755 		}
756 
757 		@Nullable
758 		@Override
759 		public String getRepoName() {
760 			if (repoComboBox.getSelectedItem() instanceof RepositoryComboBoxItem) {
761 				return ((RepositoryComboBoxItem) repoComboBox.getSelectedItem()).getRepository().getName();
762 			} else {
763 				return null;
764 			}
765 		}
766 
767 		@Nullable
768 		@Override
769 		public State getState() {
770 			return null;
771 		}
772 
773 		@Override
774 		public boolean isAllowReviewerToJoin() {
775 			return allowCheckBox.isSelected();
776 		}
777 	}
778 
779 
780 	protected abstract Review createReview(ServerData server, ReviewProvider reviewProvider)
781 			throws RemoteApiException,
782 			ServerPasswordNotProvidedException;
783 
784 	@Override
785 	protected void doOKAction() {
786 		runCreateReviewTask(true);
787 		super.doOKAction();
788 	}
789 
790 	protected void setReviewCreationTimeout(int reviewCreationTimeout) {
791 		this.reviewCreationTimeout = reviewCreationTimeout;
792 	}
793 
794 	protected void runCreateReviewTask(final boolean runUntilSuccessful) {
795 		final ServerComboBoxItem selectedItem = (ServerComboBoxItem) crucibleServersComboBox.getSelectedItem();
796 		if (selectedItem != null) {
797 			final Date startDate = new Date();
798 
799 			final ServerData server = selectedItem.getServer();
800 
801 			Task.Backgroundable changesTask = new Task.Backgroundable(project, "Creating review...", runUntilSuccessful) {
802 
803 				public boolean isCancelled = false;
804 
805 				@Override
806 				public void run(@NotNull final ProgressIndicator indicator) {
807 
808 					boolean submissionSuccess = false;
809 					do {
810 						indicator.setText("Attempting to create review... ");
811 						ModalityState modalityState = ModalityState
812 								.stateForComponent(CrucibleReviewCreateForm.this.getRootComponent());
813 
814 						Review newlyCreated = null;
815 						try {
816 							final Review draftReview = createReview(server, new ReviewProvider(server));
817 							if (draftReview == null) {
818 								EventQueue.invokeLater(new Runnable() {
819 									public void run() {
820 										Messages.showErrorDialog(
821 												project, "Review not created. Null returned.", PluginUtil.PRODUCT_NAME);
822 									}
823 								});
824 								return;
825 							}
826 							submissionSuccess = true;
827 
828 							Set<String> users = new HashSet<String>();
829 							for (int i = 0; i < model.getSize(); ++i) {
830 								UserListItem item = (UserListItem) model.get(i);
831 								if (item.isSelected()) {
832 									users.add(item.getUser().getUserName());
833 								}
834 							}
835 
836 							if (!users.isEmpty()) {
837 								crucibleServerFacade.addReviewers(server, draftReview.getPermId(), users);
838 							}
839 
840 							if (!leaveAsDraftCheckBox.isSelected()) {
841 								try {
842 									Review newReview = crucibleServerFacade.getReview(server, draftReview.getPermId());
843 									if (newReview.getModerator().getUserName().equals(server.getUserName())) {
844 										if (newReview.getActions().contains(CrucibleAction.APPROVE)) {
845 											newlyCreated = crucibleServerFacade.approveReview(server, draftReview.getPermId());
846 										} else {
847 											Messages.showErrorDialog(project,
848 													newReview.getAuthor().getDisplayName() +
849 															" is authorized to approve review.\n"
850 															+ "Leaving review in draft state.", "Permission denied");
851 										}
852 									} else {
853 										if (newReview.getActions().contains(CrucibleAction.SUBMIT)) {
854 											newlyCreated = crucibleServerFacade.submitReview(server, draftReview.getPermId());
855 										} else {
856 											Messages.showErrorDialog(project,
857 													newReview.getAuthor().getDisplayName() + " is authorized submit review.\n"
858 															+ "Leaving review in draft state.", "Permission denied");
859 										}
860 									}
861 								} catch (ValueNotYetInitialized valueNotYetInitialized) {
862 									Messages.showErrorDialog(project,
863 											"Unable to change review state. Leaving review in draft state.",
864 											"Permission denied");
865 								}
866 							} else {
867 								newlyCreated = draftReview;
868 							}
869 
870 							final Review newRevewFinal = newlyCreated != null
871 									? crucibleServerFacade.getReview(server, newlyCreated.getPermId()) : null;
872 
873 							ApplicationManager.getApplication().invokeLater(new Runnable() {
874 								public void run() {
875 									final ReviewListToolWindowPanel panel = IdeaHelper.getReviewListToolWindowPanel(project);
876 									if (panel != null && newRevewFinal != null) {
877 										panel.refresh(UpdateReason.REFRESH);
878 										panel.openReview(new ReviewAdapter(newRevewFinal, server), true);
879 									}
880 								}
881 							}, modalityState);
882 						} catch (final Throwable e) {
883 							if (!runUntilSuccessful) {
884 								ApplicationManager.getApplication().invokeAndWait(new Runnable() {
885 									public void run() {
886 										String message = "Error creating review: " + server.getUrl();
887 										if (isUnknownChangeSetException(e)) {
888 											message
889 													+= "\nSpecified change set could not be found on server. Check selected repository";
890 										}
891 										DialogWithDetails.showExceptionDialog(project, message, e);
892 									}
893 								}, modalityState);
894 							} else {
895 								if (isUnknownChangeSetException(e)) {
896 									try {
897 										Date now = new Date();
898 										if (reviewCreationTimeout > 0
899 												&& now.getTime() - startDate.getTime() >
900 												reviewCreationTimeout * MILLISECONDS_IN_MINUTE) {
901 											SwingUtilities.invokeLater(new Runnable() {
902 												public void run() {
903 													Messages.showErrorDialog(project,
904 															"Creation of the review on server\n"
905 																	+ selectedItem.getServer().getName()
906 																	+ " timed out after "
907 																	+ reviewCreationTimeout + " minutes",
908 															"Review Creation Timeout");
909 												}
910 											});
911 											break;
912 										}
913 										indicator.setText("Waiting for Crucible to update to newest change set...");
914 										for (int i = 0; i < 10; ++i) {
915 											if (indicator.isCanceled()) {
916 												isCancelled = true;
917 												break;
918 											}
919 											Thread.sleep(1000);
920 										}
921 									} catch (InterruptedException e1) {
922 										// eeeem, now what?
923 									}
924 								} else {
925 									ApplicationManager.getApplication().invokeAndWait(new Runnable() {
926 										public void run() {
927 											DialogWithDetails.showExceptionDialog(project,
928 													"Error creating review: " + server.getUrl(), e);
929 										}
930 									}, modalityState);
931 									isCancelled = true;
932 								}
933 							}
934 						}
935 					} while (runUntilSuccessful && !submissionSuccess && !isCancelled && !indicator.isCanceled());
936 				}
937 
938 				private boolean isUnknownChangeSetException(Throwable e) {
939 					return e != null
940 							&& e.getMessage() != null
941 							&& e.getMessage().contains("Specified change set id does not exist");
942 
943 				}
944 			};
945 			ProgressManager.getInstance().run(changesTask);
946 		}
947 	}
948 
949 	protected boolean isValid(ReviewProvider reviewProvider) {
950 		return true;
951 	}
952 
953 	private boolean isValidForm() {
954 		if (crucibleServersComboBox.getSelectedItem() instanceof ServerComboBoxItem && titleText.getText().length() > 0
955 				&& projectsComboBox.getSelectedItem() instanceof ProjectComboBoxItem
956 				&& authorComboBox.getSelectedItem() instanceof UserComboBoxItem
957 				&& moderatorComboBox.getSelectedItem() instanceof UserComboBoxItem) {
958 			final ServerComboBoxItem selectedItem = (ServerComboBoxItem) crucibleServersComboBox.getSelectedItem();
959 			return isValid(new ReviewProvider(selectedItem.getServer()));
960 		} else {
961 			return false;
962 		}
963 	}
964 
965 	private void createUIComponents() {
966 		model = new DefaultListModel();
967 		reviewersList = new JList(model);
968 		reviewersList.setCellRenderer(cellRenderer);
969 		reviewersList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
970 	}
971 }
972