gt: 		Cloud cloud = getMainFrame().bugCollection.getCloud();
context: /*
   * FindBugs - Find Bugs in Java programs
   * Copyright (C) 2006, University of Maryland
   * 
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   * 
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   * 
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  package edu.umd.cs.findbugs.gui2;
  
  import java.awt.Color;
  import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.ItemEvent;
  import java.awt.event.ItemListener;
  import java.net.URL;
  import java.text.SimpleDateFormat;
  import java.util.ArrayList;
  import java.util.LinkedList;
  
  import javax.swing.JButton;
  import javax.swing.JComboBox;
  import javax.swing.JLabel;
  import javax.swing.JMenu;
  import javax.swing.JMenuItem;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.JTextArea;
  import javax.swing.SwingUtilities;
  import javax.swing.event.DocumentEvent;
  import javax.swing.event.DocumentListener;
  import javax.swing.tree.TreePath;
  
  import edu.umd.cs.findbugs.BugCollection;
  import edu.umd.cs.findbugs.BugInstance;
  import edu.umd.cs.findbugs.I18N;
  import edu.umd.cs.findbugs.annotations.CheckForNull;
  import edu.umd.cs.findbugs.cloud.Cloud;
  import edu.umd.cs.findbugs.cloud.Cloud.BugFilingStatus;
  import edu.umd.cs.findbugs.util.LaunchBrowser;
  
  /**
   * @author pugh
   */
  public class CommentsArea {
  
  	private JTextArea userCommentsText = new JTextArea();
  	private JTextArea reportText = new JTextArea();
  
  	private Color userCommentsTextUnenabledColor;
  
  	private JComboBox designationComboBox;
  
  	private ArrayList<String> designationKeys;
  
  	private JButton fileBug; 
  
  	LinkedList<String> prevCommentsList = new LinkedList<String>();
  
  	final static private int prevCommentsMaxSize = 10;
  
  	private JComboBox prevCommentsComboBox; 
  
  	private boolean dontShowAnnotationConfirmation = false;
  
  	private boolean changed;
  	final MainFrame frame;
  
  	CommentsArea(MainFrame frame) {
  		this.frame = frame;
  	}
  
  	/**
  	 * Create center panel that holds the user input combo boxes and TextArea.
  	 */
  	JPanel createCommentsInputPanel() {
  		BugCollection bc = getMainFrame().bugCollection;
  		
  		Cloud cloud = bc == null ? null : bc.getCloud();
  		
  		JPanel centerPanel = new JPanel();
  		GridBagLayout layout = new GridBagLayout();
  
  		centerPanel.setLayout(layout);
  
  		userCommentsText.getDocument().addDocumentListener(
  				new DocumentListener() {
  
  					public void insertUpdate(DocumentEvent e) {
  						setProjectChanged(true);
  						changed = true;
  					}
  
  					public void removeUpdate(DocumentEvent e) {
  						setProjectChanged(true);
  						changed = true;
  					}
  
  					public void changedUpdate(DocumentEvent e) {
  						changed = true;
  					}
  
  				});
  
  		userCommentsTextUnenabledColor = centerPanel.getBackground();
  
  		userCommentsText.setLineWrap(true);
  		userCommentsText
  				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.enter_comments", "Enter your comments about this bug here"));
  		userCommentsText.setWrapStyleWord(true);
  		userCommentsText.setEnabled(false);
  		userCommentsText.setBackground(userCommentsTextUnenabledColor);
  		JScrollPane commentsScrollP = new JScrollPane(userCommentsText);
  		
  		reportText.setLineWrap(true);
  		reportText
  				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.report", "Information about the bug here"));
  		reportText.setWrapStyleWord(true);
  		reportText.setEditable(false);
  		
  		JScrollPane reportScrollP = new JScrollPane(reportText);
  		fileBug = new JButton(BugFilingStatus.FILE_BUG.toString());
  		fileBug.setEnabled(false);
  		fileBug.setToolTipText("Click to file bug for this issue");
  		fileBug.addActionListener(new ActionListener(){
  
  			public void actionPerformed(ActionEvent e) {
  				if (frame.currentSelectedBugLeaf != null) {
  					saveComments();
  					BugInstance bug = frame.currentSelectedBugLeaf.getBug();
  					Cloud cloud = getMainFrame().bugCollection.getCloud();
  					if (!cloud.supportsBugLinks())
  						return;
  					URL u = cloud.getBugLink(bug);
  					if (u != null) {
  						if (LaunchBrowser.showDocument(u)) {
  							cloud.bugFiled(bug, null);
  							getMainFrame().syncBugInformation();
  						}
  					}
  				}
  	            
              }});
  		
  		 prevCommentsComboBox = new JComboBox();
  		prevCommentsComboBox.setEnabled(false);
  		prevCommentsComboBox
  				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.reuse_comments", "Use this to reuse a previous textual comment for this bug"));
  		prevCommentsComboBox.addItemListener(new ItemListener() {
  			public void itemStateChanged(ItemEvent e) {
  				if (e.getStateChange() == ItemEvent.SELECTED
  						&& prevCommentsComboBox.getSelectedIndex() != 0) {
  					setCurrentUserCommentsText(getCurrentPrevCommentsSelection());
  
  					prevCommentsComboBox.setSelectedIndex(0);
  				}
  			}
  		});
  
  		designationComboBox = new JComboBox();
  		designationKeys = new ArrayList<String>();
  
  		designationComboBox.setEnabled(false);
  		designationComboBox
  				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.select_designation", "Select a user designation for this bug"));
  		designationComboBox.addItemListener(new ItemListener() {
  			public void itemStateChanged(ItemEvent e) {
  				if (frame.userInputEnabled
  						&& e.getStateChange() == ItemEvent.SELECTED) {
  					if (frame.currentSelectedBugLeaf == null)
  						setDesignationNonLeaf(designationComboBox
  								.getSelectedItem().toString());
  					else if (!alreadySelected())
  						setDesignation(designationComboBox.getSelectedItem()
  								.toString());
  				}
  			}
  
  			/*
  			 * Checks to see if the designation is already selected as that.
  			 * This was created because it was found the itemStateChanged method
  			 * is called when the combo box is set when a bug is clicked.
  			 */
  			private boolean alreadySelected() {
  				return designationKeys.get(
  						designationComboBox.getSelectedIndex()).equals(
  						frame.currentSelectedBugLeaf.getBug()
  								.getUserDesignationKey());
  			}
  		});
  
  		designationKeys.add("");
  		designationComboBox.addItem("");
  		for (String s : I18N.instance().getUserDesignationKeys(true)) {
  			designationKeys.add(s);
  			designationComboBox.addItem(Sortables.DESIGNATION.formatValue(s));
  		}
  		setUnknownDesignation();
  
  		//JPanel comments = new JPanel();
  		// comments.setLayout(new FlowLayout(FlowLayout.LEFT));
  		// comments.add(designationComboBox);
  		// comments.add(whoWhen);
  		
  		GridBagConstraints c = new GridBagConstraints();
  		c.gridx = 0;
  		c.gridy = 0;
  		c.fill=GridBagConstraints.HORIZONTAL;
  		c.weightx = 0;
  		centerPanel.add(new JLabel("Classify:"), c);
  		
  		c.gridx++;
  		c.fill=GridBagConstraints.HORIZONTAL;
  		c.weightx = 1;
  		
  		centerPanel.add(designationComboBox, c);
  		
  		
  		c.gridx = 0;
  		c.gridy++;
  		c.weightx = 1;
  		c.weighty = 2;
  		c.gridwidth = 2;
  		c.fill=GridBagConstraints.BOTH;
  		centerPanel.add(commentsScrollP, c);
  		
  		if (cloud != null && cloud.supportsCloudReports()) {
  			c.gridy++;
  			c.weightx = 1;
  			c.weighty = 1;
  			c.fill=GridBagConstraints.BOTH;
  		
  			centerPanel.add(reportScrollP, c);
  		}
  		
  		if (cloud != null && cloud.supportsBugLinks()) {
  			c.gridy++;
  			c.weightx = 0;
  			c.weighty = 0;
  			c.fill = GridBagConstraints.HORIZONTAL;
  			centerPanel.add(fileBug, c);
  		}
  		
  		return centerPanel;
  	}
  
  	void setUnknownDesignation() {
  		assert designationComboBox.getItemCount() == designationKeys.size();
  		designationComboBox.setSelectedIndex(0); // WARNING: this is hard
  													// coded in here.
  	}
  
  	/**
  	 * Sets the user comment panel to whether or not it is enabled. If isEnabled
  	 * is false will clear the user comments text pane.
  	 * 
  	 * @param isEnabled
  	 */
  	void setUserCommentInputEnable(final boolean isEnabled) {
  		SwingUtilities.invokeLater(new Runnable() {
  			public void run() {
  				setUserCommentInputEnableFromSwingThread(isEnabled);
  			}
  		});
  	}
  
  	/**
  	 * Sets the user comment panel to whether or not it is enabled. If isEnabled
  	 * is false will clear the user comments text pane.
  	 * 
  	 * @param isEnabled
  	 */
  	void setUserCommentInputEnableFromSwingThread(final boolean isEnabled) {
  		frame.userInputEnabled = isEnabled;
  		if (!isEnabled) {
  //			This so if already saved doesn't make it seem project changed
  			boolean b = frame.getProjectChanged();
  			userCommentsText.setText("");
  			// WARNING: this is hard coded in here, but needed
  			// so when not enabled shows default setting of designation
  			setUnknownDesignation();
  			userCommentsText.setBackground(userCommentsTextUnenabledColor);
  			setProjectChanged(b);
  		} else
  			userCommentsText.setBackground(Color.WHITE);
  		userCommentsText.setEnabled(isEnabled);
  		prevCommentsComboBox.setEnabled(isEnabled);
  		designationComboBox.setEnabled(isEnabled);
  	}
  
  
  	SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd");
  	/**
  	 * Updates comments tab. Takes node passed and sets the designation and
  	 * comments.
  	 * 
  	 * @param node
  	 */
  	void updateCommentsFromLeafInformation(final BugLeafNode node) {
  		SwingUtilities.invokeLater(new Runnable() {
  
  			public void run() {
  				//This so if already saved doesn't make it seem project changed
  				boolean b = frame.getProjectChanged();
  				BugInstance bug = node.getBug();
  				Cloud plugin = getCloud();
  
  				if (plugin.supportsBugLinks()) {
  					BugFilingStatus status = plugin.getBugLinkStatus(bug);
  					fileBug.setText(status.toString());
  					fileBug.setEnabled(status.linkEnabled());
  				} else {
  					fileBug.setEnabled(false);
  				}
  				if (!plugin.canStoreUserAnnotation(bug)) {
  					designationComboBox.setSelectedIndex(0);
  					setCurrentUserCommentsText("");
  					reportText.setText("Issue not persisted to database");
  					setUserCommentInputEnableFromSwingThread(false);
  				} else {
  					setCurrentUserCommentsText(bug.getAnnotationText());
  					if (plugin.supportsCloudReports()) {
  						String report = plugin.getCloudReport(bug);
  						reportText.setText(report);
  					}
  					designationComboBox.setSelectedIndex(designationKeys.indexOf(bug.getUserDesignationKey()));
  					setUserCommentInputEnableFromSwingThread(plugin.canStoreUserAnnotation(bug));
  				}
  				changed = false;
  				setProjectChanged(b);
  			}
  		});
  	}
  
  	void updateCommentsFromNonLeafInformation(final BugAspects theAspects) {
  		SwingUtilities.invokeLater(new Runnable() {
  			public void run() {
  				//This so if already saved doesn't make it seem project changed
  				boolean b = frame.getProjectChanged();
  				updateCommentsFromNonLeafInformationFromSwingThread(theAspects);
  				setUserCommentInputEnableFromSwingThread(true);
  				changed = false;
  				setProjectChanged(b);
  			}
  		});
  	}
  
  	/**
  	 * Saves the current comments to the BugLeafNode passed in. If the passed in
  	 * node's annotation is already equal to the current user comment then will
  	 * not do anything so setProjectedChanged is not made true. Will also add
  	 * the comment if it is new to the previous comments list.
  	 * 
  	 * @param node
  	 */
  	private void saveCommentsToBug(BugLeafNode node) {
  		if (node == null)
  			return;
  
  		String comments = getCurrentUserCommentsText();
  		final BugInstance bug = node.getBug();
  		if (bug.getAnnotationText().equals(comments))
  			return;
  
  		bug.setAnnotationText(comments, MainFrame.getInstance().bugCollection);
  		setProjectChanged(true);
  		changed = false;
  		addToPrevComments(comments);
  	}
  
  	private boolean confirmAnnotation() {
  
  		String[] options = { edu.umd.cs.findbugs.L10N.getLocalString("dlg.yes_btn", "Yes"), edu.umd.cs.findbugs.L10N.getLocalString("dlg.no_btn", "No"), edu.umd.cs.findbugs.L10N.getLocalString("dlg.yes_dont_ask_btn", "Yes, and don't ask me this again")};
  		if (dontShowAnnotationConfirmation)
  			return true;
  		int choice = JOptionPane
  				.showOptionDialog(
  						frame,
  						edu.umd.cs.findbugs.L10N.getLocalString("dlg.changing_text_lbl", "Changing this text box will overwrite the annotations associated with all bugs in this folder and subfolders. Are you sure?"),
  						edu.umd.cs.findbugs.L10N.getLocalString("dlg.annotation_change_ttl", "Annotation Change"), JOptionPane.DEFAULT_OPTION,
  						JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
  		switch (choice) {
  		case 0:
  			return true;
  		case 1:
  			return false;
  		case 2:
  			dontShowAnnotationConfirmation = true;
  			return true;
  		default:
  			return true;
  		}
  
  	}
  
  	private void saveCommentsToNonLeaf(BugAspects aspects) {
  		if (aspects == null)
  			return;
  		if (!changed) return;
  		String newComment = getCurrentUserCommentsText();
  		if (newComment.equals(getNonLeafCommentsText(aspects)))
  			return;
  		else if (confirmAnnotation()) {
  			BugSet filteredSet = aspects
  					.getMatchingBugs(BugSet.getMainBugSet());
  			for (BugLeafNode nextNode : filteredSet) {
  				saveCommentsToBug(nextNode);
  			}
  		}
  		changed = false;
  
  	}
  
  	/**
  	 * Saves comments to the current selected bug.
  	 * 
  	 */
  
  	public void saveComments() {
  		saveComments(frame.currentSelectedBugLeaf,
  				frame.currentSelectedBugAspects);
  	}
  
  	public void saveComments(BugLeafNode theNode, BugAspects theAspects) {
  		if (theNode != null)
  			saveCommentsToBug(theNode);
  		else
  			saveCommentsToNonLeaf(theAspects);
  	}
  
  	/**
  	 * Deletes the list have already. Then loads from list. Will load from the
  	 * list until run out of room in the prevCommentsList.
  	 * 
  	 * @param list
  	 */
  	void loadPrevCommentsList(String[] list) {
  		int count = 0;
  		for (String str : list) {
  			if (str.equals(""))
  				count++;
  		}
  
  		String[] ary = new String[list.length - count];
  		int j = 0;
  		for (String str : list) {
  			if (!str.equals("")) {
  				ary[j] = str;
  				j++;
  			}
  		}
  
  		String[] temp;
  		prevCommentsList = new LinkedList<String>();
  		if ((ary.length) > prevCommentsMaxSize) {
  			temp = new String[prevCommentsMaxSize];
  			for (int i = 0; i < temp.length && i < ary.length; i++)
  				temp[i] = ary[i];
  		} else {
  			temp = new String[ary.length];
  			for (int i = 0; i < ary.length; i++)
  				temp[i] = ary[i];
  		}
  
  		for (String str : temp)
  			prevCommentsList.add(str);
  
  		resetPrevCommentsComboBox();
  	}
  
  	/**
  	 * Adds the comment into the list. If the comment is already in the list
  	 * then simply moves to the front. If the list is too big when adding the
  	 * comment then deletes the last comment on the list.
  	 * 
  	 * @param comment
  	 */
  	private void addToPrevComments(String comment) {
  		if (comment.equals(""))
  			return;
  
  		if (prevCommentsList.contains(comment)) {
  			int index = prevCommentsList.indexOf(comment);
  			if (index == 0)
  				return;
  			prevCommentsList.remove(index);
  		}
  
  		prevCommentsList.addFirst(comment);
  
  		while (prevCommentsList.size() > prevCommentsMaxSize)
  			prevCommentsList.removeLast();
  
  		resetPrevCommentsComboBox();
  	}
  
  	/**
  	 * Removes all items in the comboBox for previous comments. Then refills it
  	 * using prevCommentsList.
  	 * 
  	 */
  	private void resetPrevCommentsComboBox() {
  		prevCommentsComboBox.removeAllItems();
  
  		prevCommentsComboBox.addItem("");
  
  		for (String str : prevCommentsList) {
  			if (str.length() < 20)
  				prevCommentsComboBox.addItem(str);
  			else
  				prevCommentsComboBox.addItem(str.substring(0, 17) + "...");
  		}
  	}
  
  	/**
  	 * Returns the text in the current user comments textArea.
  	 * 
  	 * @return
  	 */
  	private String getCurrentUserCommentsText() {
  		return userCommentsText.getText();
  	}
  
  	/**
  	 * Sets the current user comments text area to comment.
  	 * 
  	 * @param comment
  	 */
  	private void setCurrentUserCommentsText(String comment) {
  		changed = true;
  		userCommentsText.setText(comment);
  	}
  
  	/**
  	 * Returns the current selected previous comments. Returns as an object.
  	 */
  	private String getCurrentPrevCommentsSelection() {
  		return prevCommentsList
  				.get(prevCommentsComboBox.getSelectedIndex() - 1);
  	}
  
  	void addDesignationItem(JMenu menu, final String menuName, int keyEvent) {
  		JMenuItem toggleItem = new JMenuItem(menuName);
  
  		toggleItem.addActionListener(new ActionListener() {
  			public void actionPerformed(ActionEvent arg0) {
  				if (frame.currentSelectedBugLeaf == null)
  					setDesignationNonLeaf(menuName);
  				else
  					setDesignation(menuName);
  			}
  		});
  		MainFrame.attachAcceleratorKey(toggleItem, keyEvent);
  		menu.add(toggleItem);
  	}
  
  	void setDesignation(String designationName) {
  		if (frame.currentSelectedBugLeaf == null)
  			return;
  		String designationKey = convertDesignationNameToDesignationKey(designationName);
  		if (designationKey == null)
  			return;
  		BugCollection bugCollection = MainFrame.getInstance().bugCollection;
  		BugInstance bug = frame.currentSelectedBugLeaf.getBug();
  		String oldValue = bug.getUserDesignationKey();
  		if (designationKey.equals(oldValue))
  			return;
  		Cloud plugin = bugCollection != null? bugCollection.getCloud() : null;
  		if (plugin != null && designationKey.equals("I_WILL_FIX") && plugin.supportsClaims()) {
  			String claimedBy = plugin.claimedBy(bug);
  			if (claimedBy != null && !plugin.getUser().equals(claimedBy)) {
  				int result = JOptionPane.showConfirmDialog(null, 
  						claimedBy + " has already said they will fix this issue\n"
  						+ "Do you want to also be listed as fixing this issue?\n"
  						+ "If so, please coordinate with " + claimedBy,
  					 "Issue already claimed", JOptionPane.YES_NO_CANCEL_OPTION);
  				if (result == JOptionPane.CANCEL_OPTION)
  					return;
  				if (result != JOptionPane.YES_OPTION)
  					designationKey = "MUST_FIX";	
  			}
  		}
  		if (changeDesignationOfBug(frame.currentSelectedBugLeaf, designationKey)){
  			if (plugin != null && plugin.supportsCloudReports()) {
  				String report = plugin.getCloudReport(bug);
  				reportText.setText(report);
  			}
  			changed = true;
  			setProjectChanged(true);
  		}
  		setDesignationComboBox(designationKey);
  	}
  
  	protected void setDesignationNonLeaf(String designationName) {
  		if (nonleafUpdateDepth > 0)
  			return;
  		String designationKey = convertDesignationNameToDesignationKey(designationName);
  		if (designationKey == null || frame.currentSelectedBugAspects == null)
  			return;
  